libsmb: Implement SMB_FIND_FILE_UNIX_INFO2 dir listing
[bbaumbach/samba-autobuild/.git] / source3 / libsmb / clilist.c
index 1aa5699be55a1be9b31ae2a5e301bdb95d6f53a8..ca6d8b531297afe8614ab06f80c0b27cf9d12b07 100644 (file)
 #include "../lib/util/tevent_ntstatus.h"
 #include "async_smb.h"
 #include "trans2.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+/****************************************************************************
+ Check if a returned directory name is safe.
+****************************************************************************/
+
+static NTSTATUS is_bad_name(bool windows_names, const char *name)
+{
+       const char *bad_name_p = NULL;
+
+       bad_name_p = strchr(name, '/');
+       if (bad_name_p != NULL) {
+               /*
+                * Windows and POSIX names can't have '/'.
+                * Server is attacking us.
+                */
+               return NT_STATUS_INVALID_NETWORK_RESPONSE;
+       }
+       if (windows_names) {
+               bad_name_p = strchr(name, '\\');
+               if (bad_name_p != NULL) {
+                       /*
+                        * Windows names can't have '\\'.
+                        * Server is attacking us.
+                        */
+                       return NT_STATUS_INVALID_NETWORK_RESPONSE;
+               }
+       }
+       return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Check if a returned directory name is safe. Disconnect if server is
+ sending bad names.
+****************************************************************************/
+
+NTSTATUS is_bad_finfo_name(const struct cli_state *cli,
+                       const struct file_info *finfo)
+{
+       NTSTATUS status = NT_STATUS_OK;
+       bool windows_names = true;
+
+       if (cli->requested_posix_capabilities & CIFS_UNIX_POSIX_PATHNAMES_CAP) {
+               windows_names = false;
+       }
+       if (finfo->name != NULL) {
+               status = is_bad_name(windows_names, finfo->name);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DBG_ERR("bad finfo->name\n");
+                       return status;
+               }
+       }
+       if (finfo->short_name != NULL) {
+               status = is_bad_name(windows_names, finfo->short_name);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DBG_ERR("bad finfo->short_name\n");
+                       return status;
+               }
+       }
+       return NT_STATUS_OK;
+}
 
 /****************************************************************************
  Calculate a safe next_entry_offset.
@@ -55,7 +116,7 @@ static size_t interpret_long_filename(TALLOC_CTX *ctx,
                                        const char *p,
                                        const char *pdata_end,
                                        struct file_info *finfo,
-                                       uint32 *p_resume_key,
+                                       uint32_t *p_resume_key,
                                        DATA_BLOB *p_last_name_raw)
 {
        int len;
@@ -76,17 +137,27 @@ static size_t interpret_long_filename(TALLOC_CTX *ctx,
                        if (pdata_end - base < 27) {
                                return pdata_end - base;
                        }
+                       /*
+                        * What we're returning here as ctime_ts is
+                        * actually the server create time.
+                        */
+                       finfo->btime_ts = convert_time_t_to_timespec(
+                               make_unix_date2(p+4,
+                                       smb1cli_conn_server_time_zone(
+                                               cli->conn)));
                        finfo->ctime_ts = convert_time_t_to_timespec(
-                               make_unix_date2(p+4, cli->serverzone));
+                               make_unix_date2(p+4, smb1cli_conn_server_time_zone(cli->conn)));
                        finfo->atime_ts = convert_time_t_to_timespec(
-                               make_unix_date2(p+8, cli->serverzone));
+                               make_unix_date2(p+8, smb1cli_conn_server_time_zone(cli->conn)));
                        finfo->mtime_ts = convert_time_t_to_timespec(
-                               make_unix_date2(p+12, cli->serverzone));
+                               make_unix_date2(p+12, smb1cli_conn_server_time_zone(cli->conn)));
                        finfo->size = IVAL(p,16);
                        finfo->mode = CVAL(p,24);
                        len = CVAL(p, 26);
                        p += 27;
-                       p += align_string(base_ptr, p, 0);
+                       if (recv_flags2 & FLAGS2_UNICODE_STRINGS) {
+                               p += ucs2_align(base_ptr, p, STR_UNICODE);
+                       }
 
                        /* We can safely use len here (which is required by OS/2)
                         * and the NAS-BASIC server instead of +2 or +1 as the
@@ -125,12 +196,20 @@ static size_t interpret_long_filename(TALLOC_CTX *ctx,
                        if (pdata_end - base < 31) {
                                return pdata_end - base;
                        }
+                       /*
+                        * What we're returning here as ctime_ts is
+                        * actually the server create time.
+                        */
+                       finfo->btime_ts = convert_time_t_to_timespec(
+                               make_unix_date2(p+4,
+                                       smb1cli_conn_server_time_zone(
+                                               cli->conn)));
                        finfo->ctime_ts = convert_time_t_to_timespec(
-                               make_unix_date2(p+4, cli->serverzone));
+                               make_unix_date2(p+4, smb1cli_conn_server_time_zone(cli->conn)));
                        finfo->atime_ts = convert_time_t_to_timespec(
-                               make_unix_date2(p+8, cli->serverzone));
+                               make_unix_date2(p+8, smb1cli_conn_server_time_zone(cli->conn)));
                        finfo->mtime_ts = convert_time_t_to_timespec(
-                               make_unix_date2(p+12, cli->serverzone));
+                               make_unix_date2(p+12, smb1cli_conn_server_time_zone(cli->conn)));
                        finfo->size = IVAL(p,16);
                        finfo->mode = CVAL(p,24);
                        len = CVAL(p, 30);
@@ -183,7 +262,7 @@ static size_t interpret_long_filename(TALLOC_CTX *ctx,
                        namelen = IVAL(p,0);
                        p += 4;
                        p += 4; /* EA size */
-                       slen = SVAL(p, 0);
+                       slen = CVAL(p, 0);
                        if (slen > 24) {
                                /* Bad short name length. */
                                return pdata_end - base;
@@ -226,6 +305,63 @@ static size_t interpret_long_filename(TALLOC_CTX *ctx,
                        }
                        return calc_next_entry_offset(base, pdata_end);
                }
+               case SMB_FIND_FILE_UNIX_INFO2:
+               {
+                       SMB_STRUCT_STAT *sbuf = &finfo->posix_sbuf;
+                       size_t namelen;
+
+                       if (pdata_end - base < 128) {
+                               return pdata_end - base;
+                       }
+
+                       p += 4; /* next entry offset */
+
+                       if (p_resume_key) {
+                               *p_resume_key = IVAL(p,0);
+                       }
+                       p += 4; /* fileindex */
+
+                       fetch_file_unix_basic_info2((const uint8_t *)p, sbuf);
+                       p += 116;
+
+                       finfo->mode = S_ISDIR(sbuf->st_ex_mode) ?
+                               FILE_ATTRIBUTE_DIRECTORY :
+                               FILE_ATTRIBUTE_NORMAL;
+                       if (sbuf->st_ex_mode & S_IXUSR) {
+                               finfo->mode |= FILE_ATTRIBUTE_ARCHIVE;
+                       }
+
+                       finfo->size = sbuf->st_ex_size;
+                       finfo->allocated_size =
+                               sbuf->st_ex_blksize * sbuf->st_ex_blocks;
+                       finfo->uid = sbuf->st_ex_uid;
+                       finfo->gid = sbuf->st_ex_gid;
+                       finfo->ino = sbuf->st_ex_ino;
+                       finfo->btime_ts = sbuf->st_ex_btime;
+                       finfo->mtime_ts = sbuf->st_ex_mtime;
+                       finfo->atime_ts = sbuf->st_ex_atime;
+                       finfo->ctime_ts = sbuf->st_ex_ctime;
+
+                       namelen = IVAL(p, 0);
+                       p += 4;
+
+                       if (namelen > (pdata_end - p)) {
+                               return pdata_end - base;
+                       }
+
+                       ret = clistr_pull_talloc(
+                               ctx,
+                               base_ptr,
+                               recv_flags2,
+                               &finfo->name,
+                               p,
+                               namelen,
+                               0);
+                       if (ret == (size_t)-1) {
+                               return pdata_end - base;
+                       }
+                       return calc_next_entry_offset(base, pdata_end);
+               }
        }
 
        DEBUG(1,("Unknown long filename format %d\n",level));
@@ -247,8 +383,11 @@ static bool interpret_short_filename(TALLOC_CTX *ctx,
 
        finfo->mode = CVAL(p,21);
 
+       /* We don't get birth time. */
+       finfo->btime_ts.tv_sec = 0;
+       finfo->btime_ts.tv_nsec = 0;
        /* this date is converted to GMT by make_unix_date */
-       finfo->ctime_ts.tv_sec = make_unix_date(p+22, cli->serverzone);
+       finfo->ctime_ts.tv_sec = make_unix_date(p+22, smb1cli_conn_server_time_zone(cli->conn));
        finfo->ctime_ts.tv_nsec = 0;
        finfo->mtime_ts.tv_sec = finfo->atime_ts.tv_sec = finfo->ctime_ts.tv_sec;
        finfo->mtime_ts.tv_nsec = finfo->atime_ts.tv_nsec = 0;
@@ -298,6 +437,7 @@ static struct tevent_req *cli_list_old_send(TALLOC_CTX *mem_ctx,
        struct cli_list_old_state *state;
        uint8_t *bytes;
        static const uint16_t zero = 0;
+       uint32_t usable_space;
 
        req = tevent_req_create(mem_ctx, &state, struct cli_list_old_state);
        if (req == NULL) {
@@ -311,7 +451,8 @@ static struct tevent_req *cli_list_old_send(TALLOC_CTX *mem_ctx,
        if (tevent_req_nomem(state->mask, req)) {
                return tevent_req_post(req, ev);
        }
-       state->num_asked = (cli->max_xmit - 100) / DIR_STRUCT_SIZE;
+       usable_space = cli_state_available_size(cli, 100);
+       state->num_asked = usable_space / DIR_STRUCT_SIZE;
 
        SSVAL(state->vwv + 0, 0, state->num_asked);
        SSVAL(state->vwv + 1, 0, state->attribute);
@@ -321,7 +462,7 @@ static struct tevent_req *cli_list_old_send(TALLOC_CTX *mem_ctx,
                return tevent_req_post(req, ev);
        }
        bytes[0] = 4;
-       bytes = smb_bytes_push_str(bytes, cli_ucs2(cli), mask,
+       bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), mask,
                                   strlen(mask)+1, NULL);
 
        bytes = smb_bytes_push_bytes(bytes, 5, (const uint8_t *)&zero, 2);
@@ -329,8 +470,8 @@ static struct tevent_req *cli_list_old_send(TALLOC_CTX *mem_ctx,
                return tevent_req_post(req, ev);
        }
 
-       subreq = cli_smb_send(state, state->ev, state->cli, SMBsearch,
-                             0, 2, state->vwv, talloc_get_size(bytes), bytes);
+       subreq = cli_smb_send(state, state->ev, state->cli, SMBsearch, 0, 0,
+                       2, state->vwv, talloc_get_size(bytes), bytes);
        if (tevent_req_nomem(subreq, req)) {
                return tevent_req_post(req, ev);
        }
@@ -425,14 +566,14 @@ static void cli_list_old_done(struct tevent_req *subreq)
                return;
        }
        bytes[0] = 4;
-       bytes = smb_bytes_push_str(bytes, cli_ucs2(state->cli), "",
+       bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(state->cli->conn), "",
                                   1, NULL);
        bytes = smb_bytes_push_bytes(bytes, 5, state->search_status,
                                     sizeof(state->search_status));
        if (tevent_req_nomem(bytes, req)) {
                return;
        }
-       subreq = cli_smb_send(state, state->ev, state->cli, cmd, 0,
+       subreq = cli_smb_send(state, state->ev, state->cli, cmd, 0, 0,
                              2, state->vwv, talloc_get_size(bytes), bytes);
        if (tevent_req_nomem(subreq, req)) {
                return;
@@ -468,31 +609,38 @@ static NTSTATUS cli_list_old_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
                        TALLOC_FREE(finfo);
                        return NT_STATUS_NO_MEMORY;
                }
+
+               status = is_bad_finfo_name(state->cli, finfo);
+               if (!NT_STATUS_IS_OK(status)) {
+                       smbXcli_conn_disconnect(state->cli->conn, status);
+                       TALLOC_FREE(finfo);
+                       return status;
+               }
        }
        *pfinfo = finfo;
        return NT_STATUS_OK;
 }
 
 NTSTATUS cli_list_old(struct cli_state *cli, const char *mask,
-                     uint16 attribute,
+                     uint16_t attribute,
                      NTSTATUS (*fn)(const char *, struct file_info *,
                                 const char *, void *), void *state)
 {
        TALLOC_CTX *frame = talloc_stackframe();
-       struct event_context *ev;
+       struct tevent_context *ev;
        struct tevent_req *req;
        NTSTATUS status = NT_STATUS_NO_MEMORY;
-       struct file_info *finfo;
+       struct file_info *finfo = NULL;
        size_t i, num_finfo;
 
-       if (cli_has_async_calls(cli)) {
+       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;
        }
-       ev = event_context_init(frame);
+       ev = samba_tevent_context_init(frame);
        if (ev == NULL) {
                goto fail;
        }
@@ -500,8 +648,7 @@ NTSTATUS cli_list_old(struct cli_state *cli, const char *mask,
        if (req == NULL) {
                goto fail;
        }
-       if (!tevent_req_poll(req, ev)) {
-               status = map_nt_error_from_unix(errno);
+       if (!tevent_req_poll_ntstatus(req, ev, &status)) {
                goto fail;
        }
        status = cli_list_old_recv(req, frame, &finfo);
@@ -553,6 +700,7 @@ static struct tevent_req *cli_list_trans_send(TALLOC_CTX *mem_ctx,
        struct tevent_req *req, *subreq;
        struct cli_list_trans_state *state;
        size_t param_len;
+       uint16_t additional_flags2 = 0;
 
        req = tevent_req_create(mem_ctx, &state,
                                struct cli_list_trans_state);
@@ -572,7 +720,7 @@ static struct tevent_req *cli_list_trans_send(TALLOC_CTX *mem_ctx,
 
        state->max_matches = 1366; /* Match W2k */
 
-       state->setup[0] = TRANSACT2_FINDFIRST;
+       SSVAL(&state->setup[0], 0, TRANSACT2_FINDFIRST);
 
        state->param = talloc_array(state, uint8_t, 12);
        if (tevent_req_nomem(state->param, req)) {
@@ -583,23 +731,29 @@ static struct tevent_req *cli_list_trans_send(TALLOC_CTX *mem_ctx,
        SSVAL(state->param, 2, state->max_matches);
        SSVAL(state->param, 4,
              FLAG_TRANS2_FIND_REQUIRE_RESUME
-             |FLAG_TRANS2_FIND_CLOSE_IF_END);
+             |FLAG_TRANS2_FIND_CLOSE_IF_END
+             |(cli->backup_intent ? FLAG_TRANS2_FIND_BACKUP_INTENT : 0));
        SSVAL(state->param, 6, state->info_level);
        SIVAL(state->param, 8, 0);
 
-       state->param = trans2_bytes_push_str(state->param, cli_ucs2(cli),
+       state->param = trans2_bytes_push_str(state->param, smbXcli_conn_use_unicode(cli->conn),
                                             state->mask, strlen(state->mask)+1,
                                             NULL);
        if (tevent_req_nomem(state->param, req)) {
                return tevent_req_post(req, ev);
        }
+
+       if (clistr_is_previous_version_path(state->mask, NULL, NULL, NULL)) {
+               additional_flags2 = FLAGS2_REPARSE_PATH;
+       }
+
        param_len = talloc_get_size(state->param);
 
-       subreq = cli_trans_send(state, state->ev, state->cli,
+       subreq = cli_trans_send(state, state->ev, state->cli, additional_flags2,
                                SMBtrans2, NULL, -1, 0, 0,
                                state->setup, 1, 0,
                                state->param, param_len, 10,
-                               NULL, 0, cli->max_xmit);
+                               NULL, 0, CLI_BUFFER_SIZE);
        if (tevent_req_nomem(subreq, req)) {
                return tevent_req_post(req, ev);
        }
@@ -631,6 +785,7 @@ static void cli_list_trans_done(struct tevent_req *subreq)
        DATA_BLOB last_name_raw;
        struct file_info *finfo = NULL;
        size_t param_len;
+       uint16_t additional_flags2 = 0;
 
        min_param = (state->first ? 6 : 4);
 
@@ -696,6 +851,14 @@ static void cli_list_trans_done(struct tevent_req *subreq)
                        ff_eos = true;
                        break;
                }
+
+               status = is_bad_finfo_name(state->cli, finfo);
+               if (!NT_STATUS_IS_OK(status)) {
+                       smbXcli_conn_disconnect(state->cli->conn, status);
+                       tevent_req_nterror(req, status);
+                       return;
+               }
+
                if (!state->first && (state->mask[0] != '\0') &&
                    strcsequal(finfo->name, state->mask)) {
                        DEBUG(1, ("Error: Looping in FIND_NEXT as name %s has "
@@ -736,7 +899,7 @@ static void cli_list_trans_done(struct tevent_req *subreq)
                return;
        }
 
-       state->setup[0] = TRANSACT2_FINDNEXT;
+       SSVAL(&state->setup[0], 0, TRANSACT2_FINDNEXT);
 
        param = talloc_realloc(state, state->param, uint8_t, 12);
        if (tevent_req_nomem(param, req)) {
@@ -758,7 +921,8 @@ static void cli_list_trans_done(struct tevent_req *subreq)
         * continue instead. JRA
         */
        SSVAL(param, 10, (FLAG_TRANS2_FIND_REQUIRE_RESUME
-                         |FLAG_TRANS2_FIND_CLOSE_IF_END));
+                         |FLAG_TRANS2_FIND_CLOSE_IF_END
+                         |(state->cli->backup_intent ? FLAG_TRANS2_FIND_BACKUP_INTENT : 0)));
        if (last_name_raw.length) {
                state->param = trans2_bytes_push_bytes(state->param,
                                                       last_name_raw.data,
@@ -769,7 +933,7 @@ static void cli_list_trans_done(struct tevent_req *subreq)
                data_blob_free(&last_name_raw);
        } else {
                state->param = trans2_bytes_push_str(state->param,
-                                                    cli_ucs2(state->cli),
+                                                    smbXcli_conn_use_unicode(state->cli->conn),
                                                     state->mask,
                                                     strlen(state->mask)+1,
                                                     NULL);
@@ -779,11 +943,15 @@ static void cli_list_trans_done(struct tevent_req *subreq)
        }
        param_len = talloc_get_size(state->param);
 
-       subreq = cli_trans_send(state, state->ev, state->cli,
+       if (clistr_is_previous_version_path(state->mask, NULL, NULL, NULL)) {
+               additional_flags2 = FLAGS2_REPARSE_PATH;
+       }
+
+       subreq = cli_trans_send(state, state->ev, state->cli, additional_flags2,
                                SMBtrans2, NULL, -1, 0, 0,
                                state->setup, 1, 0,
                                state->param, param_len, 10,
-                               NULL, 0, state->cli->max_xmit);
+                               NULL, 0, CLI_BUFFER_SIZE);
        if (tevent_req_nomem(subreq, req)) {
                return;
        }
@@ -812,20 +980,20 @@ NTSTATUS cli_list_trans(struct cli_state *cli, const char *mask,
                        void *private_data)
 {
        TALLOC_CTX *frame = talloc_stackframe();
-       struct event_context *ev;
+       struct tevent_context *ev;
        struct tevent_req *req;
        int i, num_finfo;
        struct file_info *finfo = NULL;
        NTSTATUS status = NT_STATUS_NO_MEMORY;
 
-       if (cli_has_async_calls(cli)) {
+       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;
        }
-       ev = event_context_init(frame);
+       ev = samba_tevent_context_init(frame);
        if (ev == NULL) {
                goto fail;
        }
@@ -875,7 +1043,7 @@ struct tevent_req *cli_list_send(TALLOC_CTX *mem_ctx,
                return NULL;
        }
 
-       if (cli->protocol <= PROTOCOL_LANMAN1) {
+       if (smbXcli_conn_protocol(cli->conn) <= PROTOCOL_LANMAN1) {
                subreq = cli_list_old_send(state, ev, cli, mask, attribute);
                state->recv_fn = cli_list_old_recv;
        } else {
@@ -922,39 +1090,52 @@ NTSTATUS cli_list_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
        return NT_STATUS_OK;
 }
 
-NTSTATUS cli_list(struct cli_state *cli, const char *mask, uint16 attribute,
+NTSTATUS cli_list(struct cli_state *cli, const char *mask, uint16_t attribute,
                  NTSTATUS (*fn)(const char *, struct file_info *, const char *,
                             void *), void *state)
 {
-       TALLOC_CTX *frame = talloc_stackframe();
-       struct event_context *ev;
+       TALLOC_CTX *frame = NULL;
+       struct tevent_context *ev;
        struct tevent_req *req;
        NTSTATUS status = NT_STATUS_NO_MEMORY;
        struct file_info *finfo;
-       size_t i, num_finfo;
+       size_t i, num_finfo = 0;
+       uint32_t caps;
        uint16_t info_level;
 
-       if (cli_has_async_calls(cli)) {
+       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+               return cli_smb2_list(cli, mask, attribute, fn, state);
+       }
+
+       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;
        }
-       ev = event_context_init(frame);
+       ev = samba_tevent_context_init(frame);
        if (ev == NULL) {
                goto fail;
        }
 
-       info_level = (cli->capabilities & CAP_NT_SMBS)
-               ? SMB_FIND_FILE_BOTH_DIRECTORY_INFO : SMB_FIND_INFO_STANDARD;
+       caps = smb1cli_conn_capabilities(cli->conn);
+
+       if (caps & CAP_UNIX) {
+               info_level = SMB_FIND_FILE_UNIX_INFO2;
+       } else if (caps & CAP_NT_SMBS) {
+               info_level = SMB_FIND_FILE_BOTH_DIRECTORY_INFO;
+       } else {
+               info_level = SMB_FIND_INFO_STANDARD;
+       }
 
        req = cli_list_send(frame, ev, cli, mask, attribute, info_level);
        if (req == NULL) {
                goto fail;
        }
-       if (!tevent_req_poll(req, ev)) {
-               status = map_nt_error_from_unix(errno);
+       if (!tevent_req_poll_ntstatus(req, ev, &status)) {
                goto fail;
        }