1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-24 21:34:56 +03:00
samba-mirror/source3/libsmb/cli_smb2_fnum.c

5185 lines
126 KiB
C
Raw Normal View History

/*
Unix SMB/CIFS implementation.
smb2 lib
Copyright (C) Jeremy Allison 2013
Copyright (C) Volker Lendecke 2013
Copyright (C) Stefan Metzmacher 2013
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
This code is a thin wrapper around the existing
cli_smb2_XXXX() functions in libcli/smb/smb2cli_XXXXX.c,
but allows the handles to be mapped to uint16_t fnums,
which are easier for smbclient to use.
*/
#include "includes.h"
#include "client.h"
#include "async_smb.h"
#include "../libcli/smb/smbXcli_base.h"
#include "cli_smb2_fnum.h"
#include "trans2.h"
#include "clirap.h"
#include "../libcli/smb/smb2_create_blob.h"
#include "libsmb/proto.h"
#include "lib/util/tevent_ntstatus.h"
#include "../libcli/security/security.h"
#include "../librpc/gen_ndr/ndr_security.h"
#include "lib/util_ea.h"
#include "librpc/gen_ndr/ndr_ioctl.h"
#include "ntioctl.h"
#include "librpc/gen_ndr/ndr_quota.h"
#include "lib/util/string_wrappers.h"
struct smb2_hnd {
uint64_t fid_persistent;
uint64_t fid_volatile;
};
/*
* Handle mapping code.
*/
/***************************************************************
Allocate a new fnum between 1 and 0xFFFE from an smb2_hnd.
Ensures handle is owned by cli struct.
***************************************************************/
static NTSTATUS map_smb2_handle_to_fnum(struct cli_state *cli,
const struct smb2_hnd *ph, /* In */
uint16_t *pfnum) /* Out */
{
int ret;
struct idr_context *idp = cli->smb2.open_handles;
struct smb2_hnd *owned_h = talloc_memdup(cli,
ph,
sizeof(struct smb2_hnd));
if (owned_h == NULL) {
return NT_STATUS_NO_MEMORY;
}
if (idp == NULL) {
/* Lazy init */
cli->smb2.open_handles = idr_init(cli);
if (cli->smb2.open_handles == NULL) {
TALLOC_FREE(owned_h);
return NT_STATUS_NO_MEMORY;
}
idp = cli->smb2.open_handles;
}
ret = idr_get_new_above(idp, owned_h, 1, 0xFFFE);
if (ret == -1) {
TALLOC_FREE(owned_h);
return NT_STATUS_NO_MEMORY;
}
*pfnum = (uint16_t)ret;
return NT_STATUS_OK;
}
/***************************************************************
Return the smb2_hnd pointer associated with the given fnum.
***************************************************************/
static NTSTATUS map_fnum_to_smb2_handle(struct cli_state *cli,
uint16_t fnum, /* In */
struct smb2_hnd **pph) /* Out */
{
struct idr_context *idp = cli->smb2.open_handles;
if (idp == NULL) {
return NT_STATUS_INVALID_PARAMETER;
}
*pph = (struct smb2_hnd *)idr_find(idp, fnum);
if (*pph == NULL) {
return NT_STATUS_INVALID_HANDLE;
}
return NT_STATUS_OK;
}
/***************************************************************
Delete the fnum to smb2_hnd mapping. Zeros out handle on
successful return.
***************************************************************/
static NTSTATUS delete_smb2_handle_mapping(struct cli_state *cli,
struct smb2_hnd **pph, /* In */
uint16_t fnum) /* In */
{
struct idr_context *idp = cli->smb2.open_handles;
struct smb2_hnd *ph;
if (idp == NULL) {
return NT_STATUS_INVALID_PARAMETER;
}
ph = (struct smb2_hnd *)idr_find(idp, fnum);
if (ph != *pph) {
return NT_STATUS_INVALID_PARAMETER;
}
idr_remove(idp, fnum);
TALLOC_FREE(*pph);
return NT_STATUS_OK;
}
/***************************************************************
Oplock mapping code.
***************************************************************/
static uint8_t flags_to_smb2_oplock(uint32_t create_flags)
{
if (create_flags & REQUEST_BATCH_OPLOCK) {
return SMB2_OPLOCK_LEVEL_BATCH;
} else if (create_flags & REQUEST_OPLOCK) {
return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
}
/* create_flags doesn't do a level2 request. */
return SMB2_OPLOCK_LEVEL_NONE;
}
/***************************************************************
Small wrapper that allows SMB2 create to return a uint16_t fnum.
***************************************************************/
struct cli_smb2_create_fnum_state {
struct cli_state *cli;
struct smb2_create_blobs in_cblobs;
struct smb2_create_blobs out_cblobs;
struct smb_create_returns cr;
uint16_t fnum;
struct tevent_req *subreq;
};
static void cli_smb2_create_fnum_done(struct tevent_req *subreq);
static bool cli_smb2_create_fnum_cancel(struct tevent_req *req);
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,
uint32_t impersonation_level,
uint32_t desired_access,
uint32_t file_attributes,
uint32_t share_access,
uint32_t create_disposition,
uint32_t create_options,
const struct smb2_create_blobs *in_cblobs)
{
struct tevent_req *req, *subreq;
struct cli_smb2_create_fnum_state *state;
size_t fname_len = 0;
const char *startp = NULL;
const char *endp = NULL;
time_t tstamp = (time_t)0;
NTSTATUS status;
req = tevent_req_create(mem_ctx, &state,
struct cli_smb2_create_fnum_state);
if (req == NULL) {
return NULL;
}
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);
}
if (cli->backup_intent) {
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 (tevent_req_nomem(new_fname, req)) {
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);
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);
return tevent_req_post(req, ev);
}
}
if (in_cblobs != NULL) {
uint32_t i;
for (i=0; i<in_cblobs->num_blobs; i++) {
struct smb2_create_blob *b = &in_cblobs->blobs[i];
status = smb2_create_blob_add(
state, &state->in_cblobs, b->tag, b->data);
if (!NT_STATUS_IS_OK(status)) {
tevent_req_nterror(req, status);
return tevent_req_post(req, ev);
}
}
}
/* SMB2 is pickier about pathnames. Ensure it doesn't
start in a '\' */
if (*fname == '\\') {
fname++;
fname_len--;
}
/* 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;
}
subreq = smb2cli_create_send(state, ev,
cli->conn,
cli->timeout,
cli->smb2.session,
cli->smb2.tcon,
fname,
flags_to_smb2_oplock(create_flags),
impersonation_level,
desired_access,
file_attributes,
share_access,
create_disposition,
create_options,
&state->in_cblobs);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, cli_smb2_create_fnum_done, req);
state->subreq = subreq;
tevent_req_set_cancel_fn(req, cli_smb2_create_fnum_cancel);
return req;
}
static void cli_smb2_create_fnum_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_create_fnum_state *state = tevent_req_data(
req, struct cli_smb2_create_fnum_state);
struct smb2_hnd h;
NTSTATUS status;
status = smb2cli_create_recv(
subreq,
&h.fid_persistent,
&h.fid_volatile, &state->cr,
state,
&state->out_cblobs);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
status = map_smb2_handle_to_fnum(state->cli, &h, &state->fnum);
if (tevent_req_nterror(req, status)) {
return;
}
tevent_req_done(req);
}
static bool cli_smb2_create_fnum_cancel(struct tevent_req *req)
{
struct cli_smb2_create_fnum_state *state = tevent_req_data(
req, struct cli_smb2_create_fnum_state);
return tevent_req_cancel(state->subreq);
}
NTSTATUS cli_smb2_create_fnum_recv(
struct tevent_req *req,
uint16_t *pfnum,
struct smb_create_returns *cr,
TALLOC_CTX *mem_ctx,
struct smb2_create_blobs *out_cblobs)
{
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)) {
state->cli->raw_status = status;
return status;
}
if (pfnum != NULL) {
*pfnum = state->fnum;
}
if (cr != NULL) {
*cr = state->cr;
}
if (out_cblobs != NULL) {
*out_cblobs = (struct smb2_create_blobs) {
.num_blobs = state->out_cblobs.num_blobs,
.blobs = talloc_move(
mem_ctx, &state->out_cblobs.blobs),
};
}
state->cli->raw_status = NT_STATUS_OK;
return NT_STATUS_OK;
}
NTSTATUS cli_smb2_create_fnum(
struct cli_state *cli,
const char *fname,
uint32_t create_flags,
uint32_t impersonation_level,
uint32_t desired_access,
uint32_t file_attributes,
uint32_t share_access,
uint32_t create_disposition,
uint32_t create_options,
const struct smb2_create_blobs *in_cblobs,
uint16_t *pfid,
struct smb_create_returns *cr,
TALLOC_CTX *mem_ctx,
struct smb2_create_blobs *out_cblobs)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
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;
}
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = cli_smb2_create_fnum_send(
frame,
ev,
cli,
fname,
create_flags,
impersonation_level,
desired_access,
file_attributes,
share_access,
create_disposition,
create_options,
in_cblobs);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = cli_smb2_create_fnum_recv(req, pfid, cr, mem_ctx, out_cblobs);
fail:
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Small wrapper that allows SMB2 close to use a uint16_t fnum.
***************************************************************/
struct cli_smb2_close_fnum_state {
struct cli_state *cli;
uint16_t fnum;
struct smb2_hnd *ph;
};
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)
{
struct tevent_req *req, *subreq;
struct cli_smb2_close_fnum_state *state;
NTSTATUS status;
req = tevent_req_create(mem_ctx, &state,
struct cli_smb2_close_fnum_state);
if (req == NULL) {
return NULL;
}
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,
state->ph->fid_volatile);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, cli_smb2_close_fnum_done, req);
return req;
}
static void cli_smb2_close_fnum_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_close_fnum_state *state = tevent_req_data(
req, struct cli_smb2_close_fnum_state);
NTSTATUS status;
status = smb2cli_close_recv(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
/* Delete the fnum -> handle mapping. */
status = delete_smb2_handle_mapping(state->cli, &state->ph,
state->fnum);
if (tevent_req_nterror(req, status)) {
return;
}
tevent_req_done(req);
}
NTSTATUS cli_smb2_close_fnum_recv(struct tevent_req *req)
{
struct cli_smb2_close_fnum_state *state = tevent_req_data(
req, struct cli_smb2_close_fnum_state);
NTSTATUS status = NT_STATUS_OK;
if (tevent_req_is_nterror(req, &status)) {
state->cli->raw_status = status;
}
tevent_req_received(req);
return status;
}
NTSTATUS cli_smb2_close_fnum(struct cli_state *cli, uint16_t fnum)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
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;
}
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = cli_smb2_close_fnum_send(frame, ev, cli, fnum);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = cli_smb2_close_fnum_recv(req);
fail:
TALLOC_FREE(frame);
return status;
}
struct cli_smb2_set_info_fnum_state {
uint8_t dummy;
};
static void cli_smb2_set_info_fnum_done(struct tevent_req *subreq);
struct tevent_req *cli_smb2_set_info_fnum_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
uint16_t fnum,
uint8_t in_info_type,
uint8_t in_info_class,
const DATA_BLOB *in_input_buffer,
uint32_t in_additional_info)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct cli_smb2_set_info_fnum_state *state = NULL;
struct smb2_hnd *ph = NULL;
NTSTATUS status;
req = tevent_req_create(
mem_ctx, &state, struct cli_smb2_set_info_fnum_state);
if (req == NULL) {
return NULL;
}
status = map_fnum_to_smb2_handle(cli, fnum, &ph);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
subreq = smb2cli_set_info_send(
state,
ev,
cli->conn,
cli->timeout,
cli->smb2.session,
cli->smb2.tcon,
in_info_type,
in_info_class,
in_input_buffer,
in_additional_info,
ph->fid_persistent,
ph->fid_volatile);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, cli_smb2_set_info_fnum_done, req);
return req;
}
static void cli_smb2_set_info_fnum_done(struct tevent_req *subreq)
{
NTSTATUS status = smb2cli_set_info_recv(subreq);
tevent_req_simple_finish_ntstatus(subreq, status);
}
NTSTATUS cli_smb2_set_info_fnum_recv(struct tevent_req *req)
{
return tevent_req_simple_recv_ntstatus(req);
}
NTSTATUS cli_smb2_set_info_fnum(
struct cli_state *cli,
uint16_t fnum,
uint8_t in_info_type,
uint8_t in_info_class,
const DATA_BLOB *in_input_buffer,
uint32_t in_additional_info)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev = NULL;
struct tevent_req *req = NULL;
NTSTATUS status = NT_STATUS_NO_MEMORY;
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;
}
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = cli_smb2_set_info_fnum_send(
frame,
ev,
cli,
fnum,
in_info_type,
in_info_class,
in_input_buffer,
in_additional_info);
if (req == NULL) {
goto fail;
}
ok = tevent_req_poll_ntstatus(req, ev, &status);
if (!ok) {
goto fail;
}
status = cli_smb2_set_info_fnum_recv(req);
fail:
TALLOC_FREE(frame);
return status;
}
struct cli_smb2_delete_on_close_state {
struct cli_state *cli;
uint8_t data[1];
DATA_BLOB inbuf;
};
static void cli_smb2_delete_on_close_done(struct tevent_req *subreq);
struct tevent_req *cli_smb2_delete_on_close_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
uint16_t fnum,
bool flag)
{
struct tevent_req *req = NULL;
struct cli_smb2_delete_on_close_state *state = NULL;
struct tevent_req *subreq = NULL;
uint8_t in_info_type;
uint8_t in_file_info_class;
req = tevent_req_create(mem_ctx, &state,
struct cli_smb2_delete_on_close_state);
if (req == NULL) {
return NULL;
}
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).
*/
in_info_type = 1;
in_file_info_class = SMB_FILE_DISPOSITION_INFORMATION - 1000;
/* Setup data array. */
SCVAL(&state->data[0], 0, flag ? 1 : 0);
state->inbuf.data = &state->data[0];
state->inbuf.length = 1;
subreq = cli_smb2_set_info_fnum_send(
state,
ev,
cli,
fnum,
in_info_type,
in_file_info_class,
&state->inbuf,
0);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq,
cli_smb2_delete_on_close_done,
req);
return req;
}
static void cli_smb2_delete_on_close_done(struct tevent_req *subreq)
{
NTSTATUS status = cli_smb2_set_info_fnum_recv(subreq);
tevent_req_simple_finish_ntstatus(subreq, status);
}
NTSTATUS cli_smb2_delete_on_close_recv(struct tevent_req *req)
{
struct cli_smb2_delete_on_close_state *state =
tevent_req_data(req,
struct cli_smb2_delete_on_close_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
state->cli->raw_status = status;
tevent_req_received(req);
return status;
}
state->cli->raw_status = NT_STATUS_OK;
tevent_req_received(req);
return NT_STATUS_OK;
}
NTSTATUS cli_smb2_delete_on_close(struct cli_state *cli, uint16_t fnum, bool flag)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
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;
}
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = cli_smb2_delete_on_close_send(frame, ev, cli, fnum, flag);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = cli_smb2_delete_on_close_recv(req);
fail:
TALLOC_FREE(frame);
return status;
}
struct cli_smb2_mkdir_state {
struct tevent_context *ev;
struct cli_state *cli;
};
static void cli_smb2_mkdir_opened(struct tevent_req *subreq);
static void cli_smb2_mkdir_closed(struct tevent_req *subreq);
struct tevent_req *cli_smb2_mkdir_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
const char *dname)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct cli_smb2_mkdir_state *state = NULL;
req = tevent_req_create(
mem_ctx, &state, struct cli_smb2_mkdir_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 */
dname, /* 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, /* share_access */
FILE_CREATE, /* 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_mkdir_opened, req);
return req;
}
static void cli_smb2_mkdir_opened(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_mkdir_state *state = tevent_req_data(
req, struct cli_smb2_mkdir_state);
NTSTATUS status;
uint16_t fnum = 0xffff;
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_mkdir_closed, req);
}
static void cli_smb2_mkdir_closed(struct tevent_req *subreq)
{
NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
tevent_req_simple_finish_ntstatus(subreq, status);
}
NTSTATUS cli_smb2_mkdir_recv(struct tevent_req *req)
{
return tevent_req_simple_recv_ntstatus(req);
}
struct cli_smb2_rmdir_state {
struct tevent_context *ev;
struct cli_state *cli;
const char *dname;
const struct smb2_create_blobs *in_cblobs;
uint16_t fnum;
NTSTATUS status;
};
static void cli_smb2_rmdir_opened1(struct tevent_req *subreq);
static void cli_smb2_rmdir_opened2(struct tevent_req *subreq);
static void cli_smb2_rmdir_disp_set(struct tevent_req *subreq);
static void cli_smb2_rmdir_closed(struct tevent_req *subreq);
struct tevent_req *cli_smb2_rmdir_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
const char *dname,
const struct smb2_create_blobs *in_cblobs)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct cli_smb2_rmdir_state *state = NULL;
req = tevent_req_create(mem_ctx, &state, struct cli_smb2_rmdir_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->cli = cli;
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 */
SMB2_IMPERSONATION_IMPERSONATION,
DELETE_ACCESS, /* 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 */
state->in_cblobs); /* in_cblobs */
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, cli_smb2_rmdir_opened1, req);
return req;
}
static void cli_smb2_rmdir_opened1(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_rmdir_state *state = tevent_req_data(
req, struct cli_smb2_rmdir_state);
NTSTATUS status;
status = cli_smb2_create_fnum_recv(
subreq, &state->fnum, NULL, NULL, NULL);
TALLOC_FREE(subreq);
if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
/*
* 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.
*/
subreq = cli_smb2_create_fnum_send(
state,
state->ev,
state->cli,
state->dname,
0, /* create_flags */
SMB2_IMPERSONATION_IMPERSONATION,
DELETE_ACCESS, /* desired_access */
FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
FILE_OPEN, /* create_disposition */
FILE_DIRECTORY_FILE|
FILE_DELETE_ON_CLOSE|
FILE_OPEN_REPARSE_POINT, /* create_options */
state->in_cblobs); /* in_cblobs */
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, cli_smb2_rmdir_opened2, req);
return;
}
if (tevent_req_nterror(req, status)) {
return;
}
subreq = cli_smb2_delete_on_close_send(
state, state->ev, state->cli, state->fnum, true);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, cli_smb2_rmdir_disp_set, req);
}
static void cli_smb2_rmdir_opened2(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_rmdir_state *state = tevent_req_data(
req, struct cli_smb2_rmdir_state);
NTSTATUS status;
status = cli_smb2_create_fnum_recv(
subreq, &state->fnum, NULL, NULL, NULL);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
subreq = cli_smb2_delete_on_close_send(
state, state->ev, state->cli, state->fnum, true);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, cli_smb2_rmdir_disp_set, req);
}
static void cli_smb2_rmdir_disp_set(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_rmdir_state *state = tevent_req_data(
req, struct cli_smb2_rmdir_state);
state->status = cli_smb2_delete_on_close_recv(subreq);
TALLOC_FREE(subreq);
/*
* Close the fd even if the set_disp failed
*/
subreq = cli_smb2_close_fnum_send(
state, state->ev, state->cli, state->fnum);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, cli_smb2_rmdir_closed, req);
}
static void cli_smb2_rmdir_closed(struct tevent_req *subreq)
{
NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
tevent_req_simple_finish_ntstatus(subreq, status);
}
NTSTATUS cli_smb2_rmdir_recv(struct tevent_req *req)
{
struct cli_smb2_rmdir_state *state = tevent_req_data(
req, struct cli_smb2_rmdir_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
return status;
}
return state->status;
}
/***************************************************************
Small wrapper that allows SMB2 to unlink a pathname.
***************************************************************/
struct cli_smb2_unlink_state {
struct tevent_context *ev;
struct cli_state *cli;
const char *fname;
const struct smb2_create_blobs *in_cblobs;
};
static void cli_smb2_unlink_opened1(struct tevent_req *subreq);
static void cli_smb2_unlink_opened2(struct tevent_req *subreq);
static void cli_smb2_unlink_closed(struct tevent_req *subreq);
struct tevent_req *cli_smb2_unlink_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
const char *fname,
const struct smb2_create_blobs *in_cblobs)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct cli_smb2_unlink_state *state = NULL;
req = tevent_req_create(mem_ctx, &state, struct cli_smb2_unlink_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->cli = cli;
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 */
SMB2_IMPERSONATION_IMPERSONATION,
DELETE_ACCESS, /* desired_access */
FILE_ATTRIBUTE_NORMAL, /* file attributes */
FILE_SHARE_READ|
FILE_SHARE_WRITE|
FILE_SHARE_DELETE, /* share_access */
FILE_OPEN, /* create_disposition */
FILE_DELETE_ON_CLOSE, /* create_options */
state->in_cblobs); /* in_cblobs */
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, cli_smb2_unlink_opened1, req);
return req;
}
static void cli_smb2_unlink_opened1(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_unlink_state *state = tevent_req_data(
req, struct cli_smb2_unlink_state);
uint16_t fnum = 0xffff;
NTSTATUS status;
status = cli_smb2_create_fnum_recv(subreq, &fnum, NULL, NULL, NULL);
TALLOC_FREE(subreq);
if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
/*
* 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.
*/
subreq = cli_smb2_create_fnum_send(
state, /* mem_ctx */
state->ev, /* tevent_context */
state->cli, /* cli_struct */
state->fname, /* filename */
0, /* create_flags */
SMB2_IMPERSONATION_IMPERSONATION,
DELETE_ACCESS, /* desired_access */
FILE_ATTRIBUTE_NORMAL, /* file attributes */
FILE_SHARE_READ|
FILE_SHARE_WRITE|
FILE_SHARE_DELETE, /* share_access */
FILE_OPEN, /* create_disposition */
FILE_DELETE_ON_CLOSE|
FILE_OPEN_REPARSE_POINT, /* create_options */
state->in_cblobs); /* in_cblobs */
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, cli_smb2_unlink_opened2, req);
return;
}
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_unlink_closed, req);
}
static void cli_smb2_unlink_opened2(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_unlink_state *state = tevent_req_data(
req, struct cli_smb2_unlink_state);
uint16_t fnum = 0xffff;
NTSTATUS status;
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_unlink_closed, req);
}
static void cli_smb2_unlink_closed(struct tevent_req *subreq)
{
NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
tevent_req_simple_finish_ntstatus(subreq, status);
}
NTSTATUS cli_smb2_unlink_recv(struct tevent_req *req)
{
return tevent_req_simple_recv_ntstatus(req);
}
/***************************************************************
Utility function to parse a SMB2_FIND_ID_BOTH_DIRECTORY_INFO reply.
***************************************************************/
static NTSTATUS parse_finfo_id_both_directory_info(const uint8_t *dir_data,
uint32_t dir_data_length,
struct file_info *finfo,
uint32_t *next_offset)
{
size_t namelen = 0;
size_t slen = 0;
size_t ret = 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;
}
if (dir_data_length < 105) {
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->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);
finfo->ino = IVAL2_TO_SMB_BIG_UINT(dir_data + 96, 0);
namelen = IVAL(dir_data + 60,0);
if (namelen > (dir_data_length - 104)) {
return NT_STATUS_INFO_LENGTH_MISMATCH;
}
slen = CVAL(dir_data + 68, 0);
if (slen > 24) {
return NT_STATUS_INFO_LENGTH_MISMATCH;
}
ret = pull_string_talloc(finfo,
dir_data,
FLAGS2_UNICODE_STRINGS,
&finfo->short_name,
dir_data + 70,
slen,
STR_UNICODE);
if (ret == (size_t)-1) {
/* Bad conversion. */
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
ret = pull_string_talloc(finfo,
dir_data,
FLAGS2_UNICODE_STRINGS,
&finfo->name,
dir_data + 104,
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;
}
return NT_STATUS_OK;
}
/*******************************************************************
Given a filename - get its directory name
********************************************************************/
static bool windows_parent_dirname(TALLOC_CTX *mem_ctx,
const char *dir,
char **parent,
const char **name)
{
char *p;
ptrdiff_t len;
p = strrchr_m(dir, '\\'); /* Find final '\\', if any */
if (p == NULL) {
if (!(*parent = talloc_strdup(mem_ctx, "\\"))) {
return false;
}
if (name) {
*name = dir;
}
return true;
}
len = p-dir;
if (!(*parent = (char *)talloc_memdup(mem_ctx, dir, len+1))) {
return false;
}
(*parent)[len] = '\0';
if (name) {
*name = p+1;
}
return true;
}
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 status;
struct cli_smb2_list_dir_data *response;
uint32_t offset;
};
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)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct cli_smb2_list_state *state = NULL;
char *parent = NULL;
s3:libsmb: cli_smb2_list() can sometimes fail initially on a connection cli_smb2_list() appears to be a slightly unique SMB operation in that it specifies the max transaction size for the response buffer size. The Python bindings highlighted a problem where if cli_smb2_list() were one of the first operations performed on the SMBv2 connection, it would fail due to insufficient credits. Because the response buffer size is (potentially) so much larger, it requires more credits (128) compared with other SMB operations. When talking to a samba DC, the connection credits seem to start off at 1, then increase by 32 for every SMB reply we receive back from the server. After cli_full_connection(), the connection has 65 credits. The cli_smb2_create_fnum() in cli_smb2_list() adds another 32 credits, but this is still less than the 128 that smb2cli_query_directory() requires. This problem doesn't happen for smbclient because the cli_cm_open() API it uses ends up sending more messages, and so the connection has more credits. This patch changes cli_smb2_list(), so it requests a smaller response buffer size if it doesn't have enough credits available for the max transaction size. smb2cli_query_directory() is already in a loop, so it can span multiple SMB messages if for some reason the transaction size isn't big enough for the listings. BUG: https://bugzilla.samba.org/show_bug.cgi?id=13736 Signed-off-by: Tim Beale <timbeale@catalyst.net.nz> Reviewed-by: Stefan Metzmacher <metze@samba.org> Autobuild-User(master): Stefan Metzmacher <metze@samba.org> Autobuild-Date(master): Thu Jan 10 02:40:16 CET 2019 on sn-devel-144
2019-01-07 05:28:12 +03:00
bool ok;
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;
if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
return tevent_req_post(req, ev);
}
ok = windows_parent_dirname(state, pathname, &parent, &state->mask);
if (!ok) {
tevent_req_oom(req);
return tevent_req_post(req, ev);
}
subreq = cli_smb2_create_fnum_send(
state, /* mem_ctx */
ev, /* ev */
cli, /* cli */
parent, /* fname */
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 */
NULL); /* 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);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
/*
* Make our caller get back to us via cli_smb2_list_recv(),
* triggering the smb2_query_directory_send()
*/
tevent_req_defer_callback(req, state->ev);
tevent_req_notify_callback(req);
}
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;
response = talloc(state, struct cli_smb2_list_dir_data);
if (tevent_req_nomem(response, req)) {
return;
}
state->status = smb2cli_query_directory_recv(
subreq, response, &response->data, &response->length);
TALLOC_FREE(subreq);
if (NT_STATUS_IS_OK(state->status)) {
state->response = response;
state->offset = 0;
tevent_req_defer_callback(req, state->ev);
tevent_req_notify_callback(req);
return;
}
TALLOC_FREE(response);
subreq = cli_smb2_close_fnum_send(
state, state->ev, state->cli, state->fnum);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, cli_smb2_list_closed, req);
}
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);
}
/*
* 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 directoy 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;
}
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);
}
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 */
SMB2_FIND_ID_BOTH_DIRECTORY_INFO, /* 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;
}
SMB_ASSERT(response->length > state->offset);
finfo = talloc_zero(mem_ctx, struct file_info);
if (finfo == NULL) {
status = NT_STATUS_NO_MEMORY;
goto fail;
}
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;
}
status = is_bad_finfo_name(state->cli, finfo);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
s3:libsmb: cli_smb2_list() can sometimes fail initially on a connection cli_smb2_list() appears to be a slightly unique SMB operation in that it specifies the max transaction size for the response buffer size. The Python bindings highlighted a problem where if cli_smb2_list() were one of the first operations performed on the SMBv2 connection, it would fail due to insufficient credits. Because the response buffer size is (potentially) so much larger, it requires more credits (128) compared with other SMB operations. When talking to a samba DC, the connection credits seem to start off at 1, then increase by 32 for every SMB reply we receive back from the server. After cli_full_connection(), the connection has 65 credits. The cli_smb2_create_fnum() in cli_smb2_list() adds another 32 credits, but this is still less than the 128 that smb2cli_query_directory() requires. This problem doesn't happen for smbclient because the cli_cm_open() API it uses ends up sending more messages, and so the connection has more credits. This patch changes cli_smb2_list(), so it requests a smaller response buffer size if it doesn't have enough credits available for the max transaction size. smb2cli_query_directory() is already in a loop, so it can span multiple SMB messages if for some reason the transaction size isn't big enough for the listings. BUG: https://bugzilla.samba.org/show_bug.cgi?id=13736 Signed-off-by: Tim Beale <timbeale@catalyst.net.nz> Reviewed-by: Stefan Metzmacher <metze@samba.org> Autobuild-User(master): Stefan Metzmacher <metze@samba.org> Autobuild-Date(master): Thu Jan 10 02:40:16 CET 2019 on sn-devel-144
2019-01-07 05:28:12 +03:00
/*
* parse_finfo_id_both_directory_info() checks for overflow,
* no need to check again here.
s3:libsmb: cli_smb2_list() can sometimes fail initially on a connection cli_smb2_list() appears to be a slightly unique SMB operation in that it specifies the max transaction size for the response buffer size. The Python bindings highlighted a problem where if cli_smb2_list() were one of the first operations performed on the SMBv2 connection, it would fail due to insufficient credits. Because the response buffer size is (potentially) so much larger, it requires more credits (128) compared with other SMB operations. When talking to a samba DC, the connection credits seem to start off at 1, then increase by 32 for every SMB reply we receive back from the server. After cli_full_connection(), the connection has 65 credits. The cli_smb2_create_fnum() in cli_smb2_list() adds another 32 credits, but this is still less than the 128 that smb2cli_query_directory() requires. This problem doesn't happen for smbclient because the cli_cm_open() API it uses ends up sending more messages, and so the connection has more credits. This patch changes cli_smb2_list(), so it requests a smaller response buffer size if it doesn't have enough credits available for the max transaction size. smb2cli_query_directory() is already in a loop, so it can span multiple SMB messages if for some reason the transaction size isn't big enough for the listings. BUG: https://bugzilla.samba.org/show_bug.cgi?id=13736 Signed-off-by: Tim Beale <timbeale@catalyst.net.nz> Reviewed-by: Stefan Metzmacher <metze@samba.org> Autobuild-User(master): Stefan Metzmacher <metze@samba.org> Autobuild-Date(master): Thu Jan 10 02:40:16 CET 2019 on sn-devel-144
2019-01-07 05:28:12 +03:00
*/
state->offset += next_offset;
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;
}
/***************************************************************
Wrapper that allows SMB2 to query a path info (basic level).
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_qpathinfo_basic(struct cli_state *cli,
const char *name,
SMB_STRUCT_STAT *sbuf,
uint32_t *attributes)
{
NTSTATUS status;
struct smb_create_returns cr;
uint16_t fnum = 0xffff;
size_t namelen = strlen(name);
if (smbXcli_conn_has_async_calls(cli->conn)) {
/*
* Can't use sync call while an async call is in flight
*/
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] == '\\') {
char *modname = talloc_strndup(talloc_tos(), name, namelen-1);
if (modname == NULL) {
return NT_STATUS_NO_MEMORY;
}
name = modname;
}
/* This is commonly used as a 'cd'. Try qpathinfo on
a directory handle first. */
status = cli_smb2_create_fnum(cli,
name,
0, /* create_flags */
SMB2_IMPERSONATION_IMPERSONATION,
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,
&fnum,
&cr,
NULL,
NULL);
if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_A_DIRECTORY)) {
/* Maybe a file ? */
status = cli_smb2_create_fnum(cli,
name,
0, /* create_flags */
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 */
0, /* create_options */
NULL,
&fnum,
&cr,
NULL,
NULL);
}
if (!NT_STATUS_IS_OK(status)) {
return status;
}
status = cli_smb2_close_fnum(cli, fnum);
ZERO_STRUCTP(sbuf);
sbuf->st_ex_atime = nt_time_to_unix_timespec(cr.last_access_time);
sbuf->st_ex_mtime = nt_time_to_unix_timespec(cr.last_write_time);
sbuf->st_ex_ctime = nt_time_to_unix_timespec(cr.change_time);
sbuf->st_ex_size = cr.end_of_file;
*attributes = cr.file_attributes;
return status;
}
struct cli_smb2_query_info_fnum_state {
DATA_BLOB outbuf;
};
static void cli_smb2_query_info_fnum_done(struct tevent_req *subreq);
struct tevent_req *cli_smb2_query_info_fnum_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
uint16_t fnum,
uint8_t in_info_type,
uint8_t in_info_class,
uint32_t in_max_output_length,
const DATA_BLOB *in_input_buffer,
uint32_t in_additional_info,
uint32_t in_flags)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct cli_smb2_query_info_fnum_state *state = NULL;
struct smb2_hnd *ph = NULL;
NTSTATUS status;
req = tevent_req_create(
mem_ctx, &state, struct cli_smb2_query_info_fnum_state);
if (req == NULL) {
return req;
}
status = map_fnum_to_smb2_handle(cli, fnum, &ph);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
subreq = smb2cli_query_info_send(
state,
ev,
cli->conn,
cli->timeout,
cli->smb2.session,
cli->smb2.tcon,
in_info_type,
in_info_class,
in_max_output_length,
in_input_buffer,
in_additional_info,
in_flags,
ph->fid_persistent,
ph->fid_volatile);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, cli_smb2_query_info_fnum_done, req);
return req;
}
static void cli_smb2_query_info_fnum_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_query_info_fnum_state *state = tevent_req_data(
req, struct cli_smb2_query_info_fnum_state);
DATA_BLOB outbuf;
NTSTATUS status;
status = smb2cli_query_info_recv(subreq, state, &outbuf);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
/*
* We have to dup the memory here because outbuf.data is not
* returned as a talloc object by smb2cli_query_info_recv.
* It's a pointer into the received buffer.
*/
state->outbuf = data_blob_dup_talloc(state, outbuf);
if ((outbuf.length != 0) &&
tevent_req_nomem(state->outbuf.data, req)) {
return;
}
tevent_req_done(req);
}
NTSTATUS cli_smb2_query_info_fnum_recv(
struct tevent_req *req, TALLOC_CTX *mem_ctx, DATA_BLOB *outbuf)
{
struct cli_smb2_query_info_fnum_state *state = tevent_req_data(
req, struct cli_smb2_query_info_fnum_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
return status;
}
*outbuf = (DATA_BLOB) {
.data = talloc_move(mem_ctx, &state->outbuf.data),
.length = state->outbuf.length,
};
return NT_STATUS_OK;
}
NTSTATUS cli_smb2_query_info_fnum(
struct cli_state *cli,
uint16_t fnum,
uint8_t in_info_type,
uint8_t in_info_class,
uint32_t in_max_output_length,
const DATA_BLOB *in_input_buffer,
uint32_t in_additional_info,
uint32_t in_flags,
TALLOC_CTX *mem_ctx,
DATA_BLOB *outbuf)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev = NULL;
struct tevent_req *req = NULL;
NTSTATUS status = NT_STATUS_NO_MEMORY;
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;
}
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = cli_smb2_query_info_fnum_send(
frame,
ev,
cli,
fnum,
in_info_type,
in_info_class,
in_max_output_length,
in_input_buffer,
in_additional_info,
in_flags);
if (req == NULL) {
goto fail;
}
ok = tevent_req_poll_ntstatus(req, ev, &status);
if (!ok) {
goto fail;
}
status = cli_smb2_query_info_fnum_recv(req, mem_ctx, outbuf);
fail:
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Helper function for pathname operations.
***************************************************************/
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)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct get_fnum_from_path_state *state = NULL;
size_t namelen = strlen(name);
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] == '\\') {
state->name = talloc_strndup(state, name, namelen-1);
if (tevent_req_nomem(state->name, req)) {
return tevent_req_post(req, ev);
}
}
subreq = cli_smb2_create_fnum_send(
state, /* mem_ctx, */
ev, /* ev */
cli, /* cli */
state->name, /* fname */
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;
}
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);
TALLOC_FREE(subreq);
if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
/*
* 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.
*/
subreq = cli_smb2_create_fnum_send(
state, /* mem_ctx, */
state->ev, /* ev */
state->cli, /* cli */
state->name, /* fname */
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)) {
subreq = cli_smb2_create_fnum_send(
state, /* mem_ctx, */
state->ev, /* ev */
state->cli, /* cli */
state->name, /* fname */
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;
}
if (tevent_req_nterror(req, status)) {
return;
}
tevent_req_done(req);
}
static void get_fnum_from_path_opened_reparse(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 = cli_smb2_create_fnum_recv(
subreq, &state->fnum, NULL, NULL, NULL);
tevent_req_simple_finish_ntstatus(subreq, status);
}
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);
}
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 (!tevent_req_is_nterror(req, &status)) {
*pfnum = state->fnum;
}
tevent_req_received(req);
return status;
}
static NTSTATUS get_fnum_from_path(struct cli_state *cli,
const char *name,
uint32_t desired_access,
uint16_t *pfnum)
{
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)) {
status = NT_STATUS_INVALID_PARAMETER;
goto fail;
}
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = get_fnum_from_path_send(frame, ev, cli, name, desired_access);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = get_fnum_from_path_recv(req, pfnum);
fail:
TALLOC_FREE(frame);
return status;
}
/***************************************************************
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)
{
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';
}
status = NT_STATUS_OK;
fail:
if (fnum != 0xffff) {
cli_smb2_close_fnum(cli, fnum);
}
cli->raw_status = status;
TALLOC_FREE(frame);
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)
{
NTSTATUS status;
uint16_t fnum = 0xffff;
struct timespec write_time_ts;
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_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)) {
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;
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.
***************************************************************/
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;
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);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
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);
}
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
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)
{
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(cli,
name,
FILE_READ_ATTRIBUTES,
&fnum);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
/* getinfo on the handle with info_type SMB2_GETINFO_FILE (1),
level 22 (SMB2_FILE_STREAM_INFORMATION). */
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);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
/* Parse the reply. */
if (!parse_streams_blob(mem_ctx,
outbuf.data,
outbuf.length,
pnum_streams,
pstreams)) {
status = NT_STATUS_INVALID_NETWORK_RESPONSE;
goto fail;
}
fail:
if (fnum != 0xffff) {
cli_smb2_close_fnum(cli, fnum);
}
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to set SMB_FILE_BASIC_INFORMATION on
a pathname.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_setpathinfo(struct cli_state *cli,
const char *name,
uint8_t in_info_type,
uint8_t in_file_info_class,
const DATA_BLOB *p_in_data)
{
NTSTATUS status;
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_WRITE_ATTRIBUTES,
&fnum);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
status = cli_smb2_set_info_fnum(
cli,
fnum,
in_info_type,
in_file_info_class,
p_in_data, /* in_input_buffer */
0); /* in_additional_info */
fail:
if (fnum != 0xffff) {
cli_smb2_close_fnum(cli, fnum);
}
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to set pathname attributes.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_setatr(struct cli_state *cli,
const char *name,
uint32_t attr,
time_t mtime)
{
uint8_t inbuf_store[40];
DATA_BLOB inbuf = data_blob_null;
/* setinfo on the handle with info_type SMB2_SETINFO_FILE (1),
level 4 (SMB_FILE_BASIC_INFORMATION - 1000). */
inbuf.data = inbuf_store;
inbuf.length = sizeof(inbuf_store);
data_blob_clear(&inbuf);
/*
* SMB1 uses attr == 0 to clear all attributes
* on a file (end up with FILE_ATTRIBUTE_NORMAL),
* and attr == FILE_ATTRIBUTE_NORMAL to mean ignore
* request attribute change.
*
* SMB2 uses exactly the reverse. Unfortunately as the
* cli_setatr() ABI is exposed inside libsmbclient,
* we must make the SMB2 cli_smb2_setatr() call
* export the same ABI as the SMB1 cli_setatr()
* which calls it. This means reversing the sense
* of the requested attr argument if it's zero
* or FILE_ATTRIBUTE_NORMAL.
*
* See BUG: https://bugzilla.samba.org/show_bug.cgi?id=12899
*/
if (attr == 0) {
attr = FILE_ATTRIBUTE_NORMAL;
} else if (attr == FILE_ATTRIBUTE_NORMAL) {
attr = 0;
}
SIVAL(inbuf.data, 32, attr);
if (mtime != 0) {
put_long_date((char *)inbuf.data + 16,mtime);
}
/* Set all the other times to -1. */
SBVAL(inbuf.data, 0, 0xFFFFFFFFFFFFFFFFLL);
SBVAL(inbuf.data, 8, 0xFFFFFFFFFFFFFFFFLL);
SBVAL(inbuf.data, 24, 0xFFFFFFFFFFFFFFFFLL);
return cli_smb2_setpathinfo(cli,
name,
1, /* in_info_type */
/* in_file_info_class */
SMB_FILE_BASIC_INFORMATION - 1000,
&inbuf);
}
/***************************************************************
Wrapper that allows SMB2 to set file handle times.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_setattrE(struct cli_state *cli,
uint16_t fnum,
time_t change_time,
time_t access_time,
time_t write_time)
{
uint8_t inbuf_store[40];
DATA_BLOB inbuf = data_blob_null;
if (smbXcli_conn_has_async_calls(cli->conn)) {
/*
* Can't use sync call while an async call is in flight
*/
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). */
inbuf.data = inbuf_store;
inbuf.length = sizeof(inbuf_store);
data_blob_clear(&inbuf);
SBVAL(inbuf.data, 0, 0xFFFFFFFFFFFFFFFFLL);
if (change_time != 0) {
put_long_date((char *)inbuf.data + 24, change_time);
}
if (access_time != 0) {
put_long_date((char *)inbuf.data + 8, access_time);
}
if (write_time != 0) {
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;
}
/***************************************************************
Wrapper that allows SMB2 to query disk attributes (size).
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_dskattr(struct cli_state *cli, const char *path,
uint64_t *bsize, uint64_t *total, uint64_t *avail)
{
NTSTATUS status;
uint16_t fnum = 0xffff;
DATA_BLOB outbuf = data_blob_null;
uint32_t sectors_per_unit = 0;
uint32_t bytes_per_sector = 0;
uint64_t total_size = 0;
uint64_t size_free = 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;
}
/* First open the top level directory. */
status = cli_smb2_create_fnum(cli,
path,
0, /* create_flags */
SMB2_IMPERSONATION_IMPERSONATION,
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,
&fnum,
NULL,
NULL,
NULL);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
/* getinfo on the returned handle with info_type SMB2_GETINFO_FS (2),
level 3 (SMB_FS_SIZE_INFORMATION). */
status = cli_smb2_query_info_fnum(
cli,
fnum,
2, /* in_info_type */
3, /* 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 != 24) {
status = NT_STATUS_INVALID_NETWORK_RESPONSE;
goto fail;
}
total_size = BVAL(outbuf.data, 0);
size_free = BVAL(outbuf.data, 8);
sectors_per_unit = IVAL(outbuf.data, 16);
bytes_per_sector = IVAL(outbuf.data, 20);
if (bsize) {
*bsize = (uint64_t)sectors_per_unit * (uint64_t)bytes_per_sector;
}
if (total) {
*total = total_size;
}
if (avail) {
*avail = size_free;
}
status = NT_STATUS_OK;
fail:
if (fnum != 0xffff) {
cli_smb2_close_fnum(cli, fnum);
}
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to query file system sizes.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_get_fs_full_size_info(struct cli_state *cli,
uint64_t *total_allocation_units,
uint64_t *caller_allocation_units,
uint64_t *actual_allocation_units,
uint64_t *sectors_per_allocation_unit,
uint64_t *bytes_per_sector)
{
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;
}
/* First open the top level directory. */
status =
cli_smb2_create_fnum(cli, "", 0, /* create_flags */
SMB2_IMPERSONATION_IMPERSONATION,
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,
&fnum,
NULL,
NULL,
NULL);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
/* getinfo on the returned handle with info_type SMB2_GETINFO_FS (2),
level 7 (SMB_FS_FULL_SIZE_INFORMATION). */
status = cli_smb2_query_info_fnum(
cli,
fnum,
SMB2_0_INFO_FILESYSTEM, /* in_info_type */
SMB_FS_FULL_SIZE_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;
}
if (outbuf.length < 32) {
status = NT_STATUS_INVALID_NETWORK_RESPONSE;
goto fail;
}
*total_allocation_units = BIG_UINT(outbuf.data, 0);
*caller_allocation_units = BIG_UINT(outbuf.data, 8);
*actual_allocation_units = BIG_UINT(outbuf.data, 16);
*sectors_per_allocation_unit = (uint64_t)IVAL(outbuf.data, 24);
*bytes_per_sector = (uint64_t)IVAL(outbuf.data, 28);
fail:
if (fnum != 0xffff) {
cli_smb2_close_fnum(cli, fnum);
}
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to query file system attributes.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_get_fs_attr_info(struct cli_state *cli, uint32_t *fs_attr)
{
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;
}
/* First open the top level directory. */
status =
cli_smb2_create_fnum(cli, "", 0, /* create_flags */
SMB2_IMPERSONATION_IMPERSONATION,
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,
&fnum,
NULL,
NULL,
NULL);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
status = cli_smb2_query_info_fnum(
cli,
fnum,
2, /* in_info_type */
5, /* 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;
}
if (outbuf.length < 12) {
status = NT_STATUS_INVALID_NETWORK_RESPONSE;
goto fail;
}
*fs_attr = IVAL(outbuf.data, 0);
fail:
if (fnum != 0xffff) {
cli_smb2_close_fnum(cli, fnum);
}
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to query file system volume info.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_get_fs_volume_info(struct cli_state *cli,
TALLOC_CTX *mem_ctx,
char **_volume_name,
uint32_t *pserial_number,
time_t *pdate)
{
NTSTATUS status;
uint16_t fnum = 0xffff;
DATA_BLOB outbuf = data_blob_null;
uint32_t nlen;
char *volume_name = 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;
}
/* First open the top level directory. */
status =
cli_smb2_create_fnum(cli, "", 0, /* create_flags */
SMB2_IMPERSONATION_IMPERSONATION,
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,
&fnum,
NULL,
NULL,
NULL);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
/* getinfo on the returned handle with info_type SMB2_GETINFO_FS (2),
level 1 (SMB_FS_VOLUME_INFORMATION). */
status = cli_smb2_query_info_fnum(
cli,
fnum,
SMB2_0_INFO_FILESYSTEM, /* in_info_type */
/* in_file_info_class */
SMB_FS_VOLUME_INFORMATION - 1000,
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;
}
if (outbuf.length < 24) {
status = NT_STATUS_INVALID_NETWORK_RESPONSE;
goto fail;
}
if (pdate) {
struct timespec ts;
ts = interpret_long_date((char *)outbuf.data);
*pdate = ts.tv_sec;
}
if (pserial_number) {
*pserial_number = IVAL(outbuf.data,8);
}
nlen = IVAL(outbuf.data,12);
if (nlen + 18 < 18) {
/* Integer wrap. */
status = NT_STATUS_INVALID_NETWORK_RESPONSE;
goto fail;
}
/*
* The next check is safe as we know outbuf.length >= 24
* from above.
*/
if (nlen > (outbuf.length - 18)) {
status = NT_STATUS_INVALID_NETWORK_RESPONSE;
goto fail;
}
pull_string_talloc(mem_ctx,
(const char *)outbuf.data,
0,
&volume_name,
outbuf.data + 18,
nlen,
STR_UNICODE);
if (volume_name == NULL) {
status = map_nt_error_from_unix(errno);
goto fail;
}
*_volume_name = volume_name;
fail:
if (fnum != 0xffff) {
cli_smb2_close_fnum(cli, fnum);
}
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
struct cli_smb2_mxac_state {
struct tevent_context *ev;
struct cli_state *cli;
const char *fname;
struct smb2_create_blobs in_cblobs;
uint16_t fnum;
NTSTATUS status;
uint32_t mxac;
};
static void cli_smb2_mxac_opened(struct tevent_req *subreq);
static void cli_smb2_mxac_closed(struct tevent_req *subreq);
struct tevent_req *cli_smb2_query_mxac_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
const char *fname)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct cli_smb2_mxac_state *state = NULL;
NTSTATUS status;
req = tevent_req_create(mem_ctx, &state, struct cli_smb2_mxac_state);
if (req == NULL) {
return NULL;
}
*state = (struct cli_smb2_mxac_state) {
.ev = ev,
.cli = cli,
.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,
data_blob(NULL, 0));
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
subreq = cli_smb2_create_fnum_send(
state,
state->ev,
state->cli,
state->fname,
0, /* create_flags */
SMB2_IMPERSONATION_IMPERSONATION,
FILE_READ_ATTRIBUTES,
0, /* file attributes */
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
FILE_OPEN,
0, /* create_options */
&state->in_cblobs);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, cli_smb2_mxac_opened, req);
return req;
}
static void cli_smb2_mxac_opened(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_mxac_state *state = tevent_req_data(
req, struct cli_smb2_mxac_state);
struct smb2_create_blobs out_cblobs = {0};
struct smb2_create_blob *mxac_blob = NULL;
NTSTATUS status;
status = cli_smb2_create_fnum_recv(
subreq, &state->fnum, NULL, state, &out_cblobs);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
mxac_blob = smb2_create_blob_find(&out_cblobs, SMB2_CREATE_TAG_MXAC);
if (mxac_blob == NULL) {
state->status = NT_STATUS_INVALID_NETWORK_RESPONSE;
goto close;
}
if (mxac_blob->data.length != 8) {
state->status = NT_STATUS_INVALID_NETWORK_RESPONSE;
goto close;
}
state->status = NT_STATUS(IVAL(mxac_blob->data.data, 0));
state->mxac = IVAL(mxac_blob->data.data, 4);
close:
subreq = cli_smb2_close_fnum_send(
state, state->ev, state->cli, state->fnum);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, cli_smb2_mxac_closed, req);
return;
}
static void cli_smb2_mxac_closed(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
NTSTATUS status;
status = cli_smb2_close_fnum_recv(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
tevent_req_done(req);
}
NTSTATUS cli_smb2_query_mxac_recv(struct tevent_req *req, uint32_t *mxac)
{
struct cli_smb2_mxac_state *state = tevent_req_data(
req, struct cli_smb2_mxac_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
return status;
}
if (!NT_STATUS_IS_OK(state->status)) {
return state->status;
}
*mxac = state->mxac;
return NT_STATUS_OK;
}
NTSTATUS cli_smb2_query_mxac(struct cli_state *cli,
const char *fname,
uint32_t *_mxac)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev = NULL;
struct tevent_req *req = NULL;
NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
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;
}
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = cli_smb2_query_mxac_send(frame, ev, cli, fname);
if (req == NULL) {
goto fail;
}
ok = tevent_req_poll_ntstatus(req, ev, &status);
if (!ok) {
goto fail;
}
status = cli_smb2_query_mxac_recv(req, _mxac);
fail:
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
struct cli_smb2_rename_fnum_state {
DATA_BLOB inbuf;
};
static void cli_smb2_rename_fnum_done(struct tevent_req *subreq);
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)
{
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 inbuf_size;
bool ok;
req = tevent_req_create(
mem_ctx, &state, struct cli_smb2_rename_fnum_state);
if (req == NULL) {
return NULL;
}
/*
* SMB2 is pickier about pathnames. Ensure it doesn't start in
* a '\'
*/
if (*fname_dst == '\\') {
fname_dst++;
}
/*
* 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 happily 2-byte align
* for larget 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;
req = tevent_req_create(
mem_ctx, &state, struct cli_smb2_rename_state);
if (req == NULL) {
return NULL;
}
/*
* Strip a MSDFS path from fname_dst if we were given one.
*/
status = cli_dfs_target_check(state,
cli,
fname_src,
fname_dst,
&fname_dst);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
state->ev = ev;
state->cli = cli;
state->fname_dst = fname_dst;
state->replace = replace;
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;
}
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;
status = get_fnum_from_path_recv(subreq, &state->fnum);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
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);
}
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);
state->rename_status = cli_smb2_rename_fnum_recv(subreq);
TALLOC_FREE(subreq);
subreq = cli_smb2_close_fnum_send(
state, state->ev, state->cli, state->fnum);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, cli_smb2_rename_closed, req);
}
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);
}
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 (!tevent_req_is_nterror(req, &status)) {
status = state->rename_status;
}
tevent_req_received(req);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to set an EA on a fnum.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_set_ea_fnum(struct cli_state *cli,
uint16_t fnum,
const char *ea_name,
const char *ea_val,
size_t ea_len)
{
NTSTATUS status;
DATA_BLOB inbuf = data_blob_null;
size_t bloblen = 0;
char *ea_name_ascii = NULL;
size_t namelen = 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;
}
/* Marshall the SMB2 EA data. */
if (ea_len > 0xFFFF) {
status = NT_STATUS_INVALID_PARAMETER;
goto fail;
}
if (!push_ascii_talloc(frame,
&ea_name_ascii,
ea_name,
&namelen)) {
status = NT_STATUS_INVALID_PARAMETER;
goto fail;
}
if (namelen < 2 || namelen > 0xFF) {
status = NT_STATUS_INVALID_PARAMETER;
goto fail;
}
bloblen = 8 + ea_len + namelen;
/* Round up to a 4 byte boundary. */
bloblen = ((bloblen + 3)&~3);
inbuf = data_blob_talloc_zero(frame, bloblen);
if (inbuf.data == NULL) {
status = NT_STATUS_NO_MEMORY;
goto fail;
}
/* namelen doesn't include the NULL byte. */
SCVAL(inbuf.data, 5, namelen - 1);
SSVAL(inbuf.data, 6, ea_len);
memcpy(inbuf.data + 8, ea_name_ascii, namelen);
memcpy(inbuf.data + 8 + namelen, ea_val, ea_len);
/* setinfo on the handle with info_type SMB2_SETINFO_FILE (1),
level 15 (SMB_FILE_FULL_EA_INFORMATION - 1000). */
status = cli_smb2_set_info_fnum(
cli,
fnum,
1, /* in_info_type */
SMB_FILE_FULL_EA_INFORMATION - 1000, /* in_file_info_class */
&inbuf, /* in_input_buffer */
0); /* in_additional_info */
fail:
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to set an EA on a pathname.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_set_ea_path(struct cli_state *cli,
const char *name,
const char *ea_name,
const char *ea_val,
size_t ea_len)
{
NTSTATUS status;
uint16_t fnum = 0xffff;
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_WRITE_EA,
&fnum);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
status = cli_set_ea_fnum(cli,
fnum,
ea_name,
ea_val,
ea_len);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
fail:
if (fnum != 0xffff) {
cli_smb2_close_fnum(cli, fnum);
}
cli->raw_status = status;
return status;
}
/***************************************************************
Wrapper that allows SMB2 to get an EA list on a pathname.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_get_ea_list_path(struct cli_state *cli,
const char *name,
TALLOC_CTX *ctx,
size_t *pnum_eas,
struct ea_struct **pea_array)
{
NTSTATUS status;
uint16_t fnum = 0xffff;
DATA_BLOB outbuf = data_blob_null;
struct ea_list *ea_list = NULL;
struct ea_list *eal = NULL;
size_t ea_count = 0;
TALLOC_CTX *frame = talloc_stackframe();
*pnum_eas = 0;
*pea_array = 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;
}
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,
&fnum);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
/* getinfo on the handle with info_type SMB2_GETINFO_FILE (1),
level 15 (SMB_FILE_FULL_EA_INFORMATION - 1000). */
status = cli_smb2_query_info_fnum(
cli,
fnum,
1, /* in_info_type */
SMB_FILE_FULL_EA_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. */
ea_list = read_nttrans_ea_list(ctx,
(const char *)outbuf.data,
outbuf.length);
if (ea_list == NULL) {
status = NT_STATUS_INVALID_NETWORK_RESPONSE;
goto fail;
}
/* Convert to an array. */
for (eal = ea_list; eal; eal = eal->next) {
ea_count++;
}
if (ea_count) {
*pea_array = talloc_array(ctx, struct ea_struct, ea_count);
if (*pea_array == NULL) {
status = NT_STATUS_NO_MEMORY;
goto fail;
}
ea_count = 0;
for (eal = ea_list; eal; eal = eal->next) {
(*pea_array)[ea_count++] = eal->ea;
}
*pnum_eas = ea_count;
}
fail:
if (fnum != 0xffff) {
cli_smb2_close_fnum(cli, fnum);
}
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to get user quota.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_get_user_quota(struct cli_state *cli,
int quota_fnum,
SMB_NTQUOTA_STRUCT *pqt)
{
NTSTATUS status;
DATA_BLOB inbuf = data_blob_null;
DATA_BLOB info_blob = data_blob_null;
DATA_BLOB outbuf = data_blob_null;
TALLOC_CTX *frame = talloc_stackframe();
unsigned sid_len;
unsigned int offset;
struct smb2_query_quota_info query = {0};
struct file_get_quota_info info = {0};
enum ndr_err_code err;
struct ndr_push *ndr_push = 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;
}
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;
info.next_entry_offset = 0;
info.sid_length = sid_len;
info.sid = pqt->sid;
err = ndr_push_struct_blob(
&info_blob,
frame,
&info,
(ndr_push_flags_fn_t)ndr_push_file_get_quota_info);
if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
status = NT_STATUS_INTERNAL_ERROR;
goto fail;
}
query.sid_list_length = info_blob.length;
ndr_push = ndr_push_init_ctx(frame);
if (!ndr_push) {
status = NT_STATUS_NO_MEMORY;
goto fail;
}
err = ndr_push_smb2_query_quota_info(ndr_push,
NDR_SCALARS | NDR_BUFFERS,
&query);
if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
status = NT_STATUS_INTERNAL_ERROR;
goto fail;
}
err = ndr_push_array_uint8(ndr_push, NDR_SCALARS, info_blob.data,
info_blob.length);
if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
status = NT_STATUS_INTERNAL_ERROR;
goto fail;
}
inbuf.data = ndr_push->data;
inbuf.length = ndr_push->offset;
status = cli_smb2_query_info_fnum(
cli,
quota_fnum,
4, /* in_info_type */
0, /* in_file_info_class */
0xFFFF, /* in_max_output_length */
&inbuf, /* in_input_buffer */
0, /* in_additional_info */
0, /* in_flags */
frame,
&outbuf);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
if (!parse_user_quota_record(outbuf.data, outbuf.length, &offset,
pqt)) {
status = NT_STATUS_INVALID_NETWORK_RESPONSE;
DEBUG(0, ("Got invalid FILE_QUOTA_INFORMATION in reply.\n"));
}
fail:
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to list user quota.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_list_user_quota_step(struct cli_state *cli,
TALLOC_CTX *mem_ctx,
int quota_fnum,
SMB_NTQUOTA_LIST **pqt_list,
bool first)
{
NTSTATUS status;
DATA_BLOB inbuf = data_blob_null;
DATA_BLOB outbuf = data_blob_null;
TALLOC_CTX *frame = talloc_stackframe();
struct smb2_query_quota_info info = {0};
enum ndr_err_code err;
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 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(
&inbuf,
frame,
&info,
(ndr_push_flags_fn_t)ndr_push_smb2_query_quota_info);
if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
status = NT_STATUS_INTERNAL_ERROR;
goto cleanup;
}
status = cli_smb2_query_info_fnum(
cli,
quota_fnum,
4, /* in_info_type */
0, /* in_file_info_class */
0xFFFF, /* in_max_output_length */
&inbuf, /* in_input_buffer */
0, /* in_additional_info */
0, /* in_flags */
frame,
&outbuf);
/*
* safeguard against panic from calling parse_user_quota_list with
* NULL buffer
*/
if (NT_STATUS_IS_OK(status) && outbuf.length == 0) {
status = NT_STATUS_NO_MORE_ENTRIES;
}
if (!NT_STATUS_IS_OK(status)) {
goto cleanup;
}
status = parse_user_quota_list(outbuf.data, outbuf.length, mem_ctx,
pqt_list);
cleanup:
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to get file system quota.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_get_fs_quota_info(struct cli_state *cli,
int quota_fnum,
SMB_NTQUOTA_STRUCT *pqt)
{
NTSTATUS status;
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 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,
2, /* in_info_type */
SMB_FS_QUOTA_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 cleanup;
}
status = parse_fs_quota_buffer(outbuf.data, outbuf.length, pqt);
cleanup:
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to set user quota.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_set_user_quota(struct cli_state *cli,
int quota_fnum,
SMB_NTQUOTA_LIST *qtl)
{
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 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;
}
status = cli_smb2_set_info_fnum(
cli,
quota_fnum,
4, /* in_info_type */
0, /* in_file_info_class */
&inbuf, /* in_input_buffer */
0); /* in_additional_info */
cleanup:
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
NTSTATUS cli_smb2_set_fs_quota_info(struct cli_state *cli,
int quota_fnum,
SMB_NTQUOTA_STRUCT *pqt)
{
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 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;
}
status = cli_smb2_set_info_fnum(
cli,
quota_fnum,
2, /* in_info_type */
SMB_FS_QUOTA_INFORMATION - 1000, /* in_file_info_class */
&inbuf, /* in_input_buffer */
0); /* in_additional_info */
cleanup:
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
struct cli_smb2_read_state {
struct tevent_context *ev;
struct cli_state *cli;
struct smb2_hnd *ph;
uint64_t start_offset;
uint32_t size;
uint32_t received;
uint8_t *buf;
};
static void cli_smb2_read_done(struct tevent_req *subreq);
struct tevent_req *cli_smb2_read_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
uint16_t fnum,
off_t offset,
size_t size)
{
NTSTATUS status;
struct tevent_req *req, *subreq;
struct cli_smb2_read_state *state;
req = tevent_req_create(mem_ctx, &state, struct cli_smb2_read_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->cli = cli;
state->start_offset = (uint64_t)offset;
state->size = (uint32_t)size;
state->received = 0;
state->buf = NULL;
status = map_fnum_to_smb2_handle(cli,
fnum,
&state->ph);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
subreq = smb2cli_read_send(state,
state->ev,
state->cli->conn,
state->cli->timeout,
state->cli->smb2.session,
state->cli->smb2.tcon,
state->size,
state->start_offset,
state->ph->fid_persistent,
state->ph->fid_volatile,
0, /* minimum_count */
0); /* remaining_bytes */
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, cli_smb2_read_done, req);
return req;
}
static void cli_smb2_read_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_read_state *state = tevent_req_data(
req, struct cli_smb2_read_state);
NTSTATUS status;
status = smb2cli_read_recv(subreq, state,
&state->buf, &state->received);
if (tevent_req_nterror(req, status)) {
return;
}
if (state->received > state->size) {
tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
return;
}
tevent_req_done(req);
}
NTSTATUS cli_smb2_read_recv(struct tevent_req *req,
ssize_t *received,
uint8_t **rcvbuf)
{
NTSTATUS status;
struct cli_smb2_read_state *state = tevent_req_data(
req, struct cli_smb2_read_state);
if (tevent_req_is_nterror(req, &status)) {
state->cli->raw_status = status;
return status;
}
/*
* As in cli_read_andx_recv() rcvbuf is talloced from the request, so
* better make sure that you copy it away before you talloc_free(req).
* "rcvbuf" is NOT a talloc_ctx of its own, so do not talloc_move it!
*/
*received = (ssize_t)state->received;
*rcvbuf = state->buf;
state->cli->raw_status = NT_STATUS_OK;
return NT_STATUS_OK;
}
struct cli_smb2_write_state {
struct tevent_context *ev;
struct cli_state *cli;
struct smb2_hnd *ph;
uint32_t flags;
const uint8_t *buf;
uint64_t offset;
uint32_t size;
uint32_t written;
};
static void cli_smb2_write_written(struct tevent_req *req);
struct tevent_req *cli_smb2_write_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
uint16_t fnum,
uint16_t mode,
const uint8_t *buf,
off_t offset,
size_t size)
{
NTSTATUS status;
struct tevent_req *req, *subreq = NULL;
struct cli_smb2_write_state *state = NULL;
req = tevent_req_create(mem_ctx, &state, struct cli_smb2_write_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->cli = cli;
/* Both SMB1 and SMB2 use 1 in the following meaning write-through. */
state->flags = (uint32_t)mode;
state->buf = buf;
state->offset = (uint64_t)offset;
state->size = (uint32_t)size;
state->written = 0;
status = map_fnum_to_smb2_handle(cli,
fnum,
&state->ph);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
subreq = smb2cli_write_send(state,
state->ev,
state->cli->conn,
state->cli->timeout,
state->cli->smb2.session,
state->cli->smb2.tcon,
state->size,
state->offset,
state->ph->fid_persistent,
state->ph->fid_volatile,
0, /* remaining_bytes */
state->flags, /* flags */
state->buf);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, cli_smb2_write_written, req);
return req;
}
static void cli_smb2_write_written(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_write_state *state = tevent_req_data(
req, struct cli_smb2_write_state);
NTSTATUS status;
uint32_t written;
status = smb2cli_write_recv(subreq, &written);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
state->written = written;
tevent_req_done(req);
}
NTSTATUS cli_smb2_write_recv(struct tevent_req *req,
size_t *pwritten)
{
struct cli_smb2_write_state *state = tevent_req_data(
req, struct cli_smb2_write_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
state->cli->raw_status = status;
tevent_req_received(req);
return status;
}
if (pwritten != NULL) {
*pwritten = (size_t)state->written;
}
state->cli->raw_status = NT_STATUS_OK;
tevent_req_received(req);
return NT_STATUS_OK;
}
/***************************************************************
Wrapper that allows SMB2 async write using an fnum.
This is mostly cut-and-paste from Volker's code inside
source3/libsmb/clireadwrite.c, adapted for SMB2.
Done this way so I can reuse all the logic inside cli_push()
for free :-).
***************************************************************/
struct cli_smb2_writeall_state {
struct tevent_context *ev;
struct cli_state *cli;
struct smb2_hnd *ph;
uint32_t flags;
const uint8_t *buf;
uint64_t offset;
uint32_t size;
uint32_t written;
};
static void cli_smb2_writeall_written(struct tevent_req *req);
struct tevent_req *cli_smb2_writeall_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
uint16_t fnum,
uint16_t mode,
const uint8_t *buf,
off_t offset,
size_t size)
{
NTSTATUS status;
struct tevent_req *req, *subreq = NULL;
struct cli_smb2_writeall_state *state = NULL;
uint32_t to_write;
uint32_t max_size;
bool ok;
req = tevent_req_create(mem_ctx, &state, struct cli_smb2_writeall_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->cli = cli;
/* Both SMB1 and SMB2 use 1 in the following meaning write-through. */
state->flags = (uint32_t)mode;
state->buf = buf;
state->offset = (uint64_t)offset;
state->size = (uint32_t)size;
state->written = 0;
status = map_fnum_to_smb2_handle(cli,
fnum,
&state->ph);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
to_write = state->size;
max_size = smb2cli_conn_max_write_size(state->cli->conn);
to_write = MIN(max_size, to_write);
ok = smb2cli_conn_req_possible(state->cli->conn, &max_size);
if (ok) {
to_write = MIN(max_size, to_write);
}
subreq = smb2cli_write_send(state,
state->ev,
state->cli->conn,
state->cli->timeout,
state->cli->smb2.session,
state->cli->smb2.tcon,
to_write,
state->offset,
state->ph->fid_persistent,
state->ph->fid_volatile,
0, /* remaining_bytes */
state->flags, /* flags */
state->buf + state->written);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, cli_smb2_writeall_written, req);
return req;
}
static void cli_smb2_writeall_written(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_writeall_state *state = tevent_req_data(
req, struct cli_smb2_writeall_state);
NTSTATUS status;
uint32_t written, to_write;
uint32_t max_size;
bool ok;
status = smb2cli_write_recv(subreq, &written);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
state->written += written;
if (state->written > state->size) {
tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
return;
}
to_write = state->size - state->written;
if (to_write == 0) {
tevent_req_done(req);
return;
}
max_size = smb2cli_conn_max_write_size(state->cli->conn);
to_write = MIN(max_size, to_write);
ok = smb2cli_conn_req_possible(state->cli->conn, &max_size);
if (ok) {
to_write = MIN(max_size, to_write);
}
subreq = smb2cli_write_send(state,
state->ev,
state->cli->conn,
state->cli->timeout,
state->cli->smb2.session,
state->cli->smb2.tcon,
to_write,
state->offset + state->written,
state->ph->fid_persistent,
state->ph->fid_volatile,
0, /* remaining_bytes */
state->flags, /* flags */
state->buf + state->written);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, cli_smb2_writeall_written, req);
}
NTSTATUS cli_smb2_writeall_recv(struct tevent_req *req,
size_t *pwritten)
{
struct cli_smb2_writeall_state *state = tevent_req_data(
req, struct cli_smb2_writeall_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
state->cli->raw_status = status;
return status;
}
if (pwritten != NULL) {
*pwritten = (size_t)state->written;
}
state->cli->raw_status = NT_STATUS_OK;
return NT_STATUS_OK;
}
struct cli_smb2_splice_state {
struct tevent_context *ev;
struct cli_state *cli;
struct smb2_hnd *src_ph;
struct smb2_hnd *dst_ph;
int (*splice_cb)(off_t n, void *priv);
void *priv;
off_t written;
off_t size;
off_t src_offset;
off_t dst_offset;
bool resized;
struct req_resume_key_rsp resume_rsp;
struct srv_copychunk_copy cc_copy;
};
static void cli_splice_copychunk_send(struct cli_smb2_splice_state *state,
struct tevent_req *req);
static void cli_splice_copychunk_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_splice_state *state =
tevent_req_data(req,
struct cli_smb2_splice_state);
struct smbXcli_conn *conn = state->cli->conn;
DATA_BLOB out_input_buffer = data_blob_null;
DATA_BLOB out_output_buffer = data_blob_null;
struct srv_copychunk_rsp cc_copy_rsp;
enum ndr_err_code ndr_ret;
NTSTATUS status;
status = smb2cli_ioctl_recv(subreq, state,
&out_input_buffer,
&out_output_buffer);
TALLOC_FREE(subreq);
if ((!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) ||
state->resized) && tevent_req_nterror(req, status)) {
return;
}
ndr_ret = ndr_pull_struct_blob(&out_output_buffer, state, &cc_copy_rsp,
(ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
if (ndr_ret != NDR_ERR_SUCCESS) {
DEBUG(0, ("failed to unmarshall copy chunk rsp\n"));
tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
return;
}
if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
uint32_t max_chunks = MIN(cc_copy_rsp.chunks_written,
cc_copy_rsp.total_bytes_written / cc_copy_rsp.chunk_bytes_written);
if ((cc_copy_rsp.chunk_bytes_written > smb2cli_conn_cc_chunk_len(conn) ||
max_chunks > smb2cli_conn_cc_max_chunks(conn)) &&
tevent_req_nterror(req, status)) {
return;
}
state->resized = true;
smb2cli_conn_set_cc_chunk_len(conn, cc_copy_rsp.chunk_bytes_written);
smb2cli_conn_set_cc_max_chunks(conn, max_chunks);
} else {
if ((state->src_offset > INT64_MAX - cc_copy_rsp.total_bytes_written) ||
(state->dst_offset > INT64_MAX - cc_copy_rsp.total_bytes_written) ||
(state->written > INT64_MAX - cc_copy_rsp.total_bytes_written)) {
tevent_req_nterror(req, NT_STATUS_FILE_TOO_LARGE);
return;
}
state->src_offset += cc_copy_rsp.total_bytes_written;
state->dst_offset += cc_copy_rsp.total_bytes_written;
state->written += cc_copy_rsp.total_bytes_written;
if (!state->splice_cb(state->written, state->priv)) {
tevent_req_nterror(req, NT_STATUS_CANCELLED);
return;
}
}
cli_splice_copychunk_send(state, req);
}
static void cli_splice_copychunk_send(struct cli_smb2_splice_state *state,
struct tevent_req *req)
{
struct tevent_req *subreq;
enum ndr_err_code ndr_ret;
struct smbXcli_conn *conn = state->cli->conn;
struct srv_copychunk_copy *cc_copy = &state->cc_copy;
off_t src_offset = state->src_offset;
off_t dst_offset = state->dst_offset;
uint32_t req_len = MIN(smb2cli_conn_cc_chunk_len(conn) * smb2cli_conn_cc_max_chunks(conn),
state->size - state->written);
DATA_BLOB in_input_buffer = data_blob_null;
DATA_BLOB in_output_buffer = data_blob_null;
if (state->size - state->written == 0) {
tevent_req_done(req);
return;
}
cc_copy->chunk_count = 0;
while (req_len) {
cc_copy->chunks[cc_copy->chunk_count].source_off = src_offset;
cc_copy->chunks[cc_copy->chunk_count].target_off = dst_offset;
cc_copy->chunks[cc_copy->chunk_count].length = MIN(req_len,
smb2cli_conn_cc_chunk_len(conn));
if (req_len < cc_copy->chunks[cc_copy->chunk_count].length) {
tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
return;
}
req_len -= cc_copy->chunks[cc_copy->chunk_count].length;
if ((src_offset > INT64_MAX - cc_copy->chunks[cc_copy->chunk_count].length) ||
(dst_offset > INT64_MAX - cc_copy->chunks[cc_copy->chunk_count].length)) {
tevent_req_nterror(req, NT_STATUS_FILE_TOO_LARGE);
return;
}
src_offset += cc_copy->chunks[cc_copy->chunk_count].length;
dst_offset += cc_copy->chunks[cc_copy->chunk_count].length;
cc_copy->chunk_count++;
}
ndr_ret = ndr_push_struct_blob(&in_input_buffer, state, cc_copy,
(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
if (ndr_ret != NDR_ERR_SUCCESS) {
DEBUG(0, ("failed to marshall copy chunk req\n"));
tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
return;
}
subreq = smb2cli_ioctl_send(state, state->ev, state->cli->conn,
state->cli->timeout,
state->cli->smb2.session,
state->cli->smb2.tcon,
state->dst_ph->fid_persistent, /* in_fid_persistent */
state->dst_ph->fid_volatile, /* in_fid_volatile */
FSCTL_SRV_COPYCHUNK_WRITE,
0, /* in_max_input_length */
&in_input_buffer,
12, /* in_max_output_length */
&in_output_buffer,
SMB2_IOCTL_FLAG_IS_FSCTL);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq,
cli_splice_copychunk_done,
req);
}
static void cli_splice_key_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_splice_state *state =
tevent_req_data(req,
struct cli_smb2_splice_state);
enum ndr_err_code ndr_ret;
NTSTATUS status;
DATA_BLOB out_input_buffer = data_blob_null;
DATA_BLOB out_output_buffer = data_blob_null;
status = smb2cli_ioctl_recv(subreq, state,
&out_input_buffer,
&out_output_buffer);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
ndr_ret = ndr_pull_struct_blob(&out_output_buffer,
state, &state->resume_rsp,
(ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp);
if (ndr_ret != NDR_ERR_SUCCESS) {
DEBUG(0, ("failed to unmarshall resume key rsp\n"));
tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
return;
}
memcpy(&state->cc_copy.source_key,
&state->resume_rsp.resume_key,
sizeof state->resume_rsp.resume_key);
cli_splice_copychunk_send(state, req);
}
struct tevent_req *cli_smb2_splice_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
uint16_t src_fnum, uint16_t dst_fnum,
off_t size, off_t src_offset, off_t dst_offset,
int (*splice_cb)(off_t n, void *priv),
void *priv)
{
struct tevent_req *req;
struct tevent_req *subreq;
struct cli_smb2_splice_state *state;
NTSTATUS status;
DATA_BLOB in_input_buffer = data_blob_null;
DATA_BLOB in_output_buffer = data_blob_null;
req = tevent_req_create(mem_ctx, &state, struct cli_smb2_splice_state);
if (req == NULL) {
return NULL;
}
state->cli = cli;
state->ev = ev;
state->splice_cb = splice_cb;
state->priv = priv;
state->size = size;
state->written = 0;
state->src_offset = src_offset;
state->dst_offset = dst_offset;
state->cc_copy.chunks = talloc_array(state,
struct srv_copychunk,
smb2cli_conn_cc_max_chunks(cli->conn));
if (state->cc_copy.chunks == NULL) {
return NULL;
}
status = map_fnum_to_smb2_handle(cli, src_fnum, &state->src_ph);
if (tevent_req_nterror(req, status))
return tevent_req_post(req, ev);
status = map_fnum_to_smb2_handle(cli, dst_fnum, &state->dst_ph);
if (tevent_req_nterror(req, status))
return tevent_req_post(req, ev);
subreq = smb2cli_ioctl_send(state, ev, cli->conn,
cli->timeout,
cli->smb2.session,
cli->smb2.tcon,
state->src_ph->fid_persistent, /* in_fid_persistent */
state->src_ph->fid_volatile, /* in_fid_volatile */
FSCTL_SRV_REQUEST_RESUME_KEY,
0, /* in_max_input_length */
&in_input_buffer,
32, /* in_max_output_length */
&in_output_buffer,
SMB2_IOCTL_FLAG_IS_FSCTL);
if (tevent_req_nomem(subreq, req)) {
return NULL;
}
tevent_req_set_callback(subreq,
cli_splice_key_done,
req);
return req;
}
NTSTATUS cli_smb2_splice_recv(struct tevent_req *req, off_t *written)
{
struct cli_smb2_splice_state *state = tevent_req_data(
req, struct cli_smb2_splice_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
state->cli->raw_status = status;
tevent_req_received(req);
return status;
}
if (written != NULL) {
*written = state->written;
}
state->cli->raw_status = NT_STATUS_OK;
tevent_req_received(req);
return NT_STATUS_OK;
}
/***************************************************************
SMB2 enum shadow copy data.
***************************************************************/
struct cli_smb2_shadow_copy_data_fnum_state {
struct cli_state *cli;
uint16_t fnum;
struct smb2_hnd *ph;
DATA_BLOB out_input_buffer;
DATA_BLOB out_output_buffer;
};
static void cli_smb2_shadow_copy_data_fnum_done(struct tevent_req *subreq);
static struct tevent_req *cli_smb2_shadow_copy_data_fnum_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
uint16_t fnum,
bool get_names)
{
struct tevent_req *req, *subreq;
struct cli_smb2_shadow_copy_data_fnum_state *state;
NTSTATUS status;
req = tevent_req_create(mem_ctx, &state,
struct cli_smb2_shadow_copy_data_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);
}
/*
* TODO. Under SMB2 we should send a zero max_output_length
* ioctl to get the required size, then send another ioctl
* to get the data, but the current SMB1 implementation just
* does one roundtrip with a 64K buffer size. Do the same
* for now. JRA.
*/
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_SHADOW_COPY_DATA,
0, /* in_max_input_length */
NULL, /* in_input_buffer */
get_names ?
CLI_BUFFER_SIZE : 16, /* in_max_output_length */
NULL, /* in_output_buffer */
SMB2_IOCTL_FLAG_IS_FSCTL);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq,
cli_smb2_shadow_copy_data_fnum_done,
req);
return req;
}
static void cli_smb2_shadow_copy_data_fnum_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_shadow_copy_data_fnum_state *state = tevent_req_data(
req, struct cli_smb2_shadow_copy_data_fnum_state);
NTSTATUS status;
status = smb2cli_ioctl_recv(subreq, state,
&state->out_input_buffer,
&state->out_output_buffer);
tevent_req_simple_finish_ntstatus(subreq, status);
}
static NTSTATUS cli_smb2_shadow_copy_data_fnum_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
bool get_names,
char ***pnames,
int *pnum_names)
{
struct cli_smb2_shadow_copy_data_fnum_state *state = tevent_req_data(
req, struct cli_smb2_shadow_copy_data_fnum_state);
char **names = NULL;
uint32_t num_names = 0;
uint32_t num_names_returned = 0;
uint32_t dlength = 0;
uint32_t i;
uint8_t *endp = NULL;
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
return status;
}
if (state->out_output_buffer.length < 16) {
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
num_names = IVAL(state->out_output_buffer.data, 0);
num_names_returned = IVAL(state->out_output_buffer.data, 4);
dlength = IVAL(state->out_output_buffer.data, 8);
if (num_names > 0x7FFFFFFF) {
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
if (get_names == false) {
*pnum_names = (int)num_names;
return NT_STATUS_OK;
}
if (num_names != num_names_returned) {
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
if (dlength + 12 < 12) {
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
/*
* NB. The below is an allowable return if there are
* more snapshots than the buffer size we told the
* server we can receive. We currently don't support
* this.
*/
if (dlength + 12 > state->out_output_buffer.length) {
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
if (state->out_output_buffer.length +
(2 * sizeof(SHADOW_COPY_LABEL)) <
state->out_output_buffer.length) {
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
names = talloc_array(mem_ctx, char *, num_names_returned);
if (names == NULL) {
return NT_STATUS_NO_MEMORY;
}
endp = state->out_output_buffer.data +
state->out_output_buffer.length;
for (i=0; i<num_names_returned; i++) {
bool ret;
uint8_t *src;
size_t converted_size;
src = state->out_output_buffer.data + 12 +
(i * 2 * sizeof(SHADOW_COPY_LABEL));
if (src + (2 * sizeof(SHADOW_COPY_LABEL)) > endp) {
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
ret = convert_string_talloc(
names, CH_UTF16LE, CH_UNIX,
src, 2 * sizeof(SHADOW_COPY_LABEL),
&names[i], &converted_size);
if (!ret) {
TALLOC_FREE(names);
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
}
*pnum_names = num_names;
*pnames = names;
return NT_STATUS_OK;
}
NTSTATUS cli_smb2_shadow_copy_data(TALLOC_CTX *mem_ctx,
struct cli_state *cli,
uint16_t fnum,
bool get_names,
char ***pnames,
int *pnum_names)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
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;
}
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = cli_smb2_shadow_copy_data_fnum_send(frame,
ev,
cli,
fnum,
get_names);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = cli_smb2_shadow_copy_data_fnum_recv(req,
mem_ctx,
get_names,
pnames,
pnum_names);
fail:
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
/***************************************************************
Wrapper that allows SMB2 to truncate a file.
Synchronous only.
***************************************************************/
NTSTATUS cli_smb2_ftruncate(struct cli_state *cli,
uint16_t fnum,
uint64_t newsize)
{
NTSTATUS status;
uint8_t buf[8] = {0};
DATA_BLOB inbuf = { .data = buf, .length = sizeof(buf) };
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;
}
SBVAL(buf, 0, newsize);
/* setinfo on the handle with info_type SMB2_SETINFO_FILE (1),
level 20 (SMB_FILE_END_OF_FILE_INFORMATION - 1000). */
status = cli_smb2_set_info_fnum(
cli,
fnum,
1, /* in_info_type */
SMB_FILE_END_OF_FILE_INFORMATION-1000, /* in_file_info_class */
&inbuf, /* in_input_buffer */
0);
fail:
cli->raw_status = status;
TALLOC_FREE(frame);
return status;
}
struct cli_smb2_notify_state {
struct tevent_req *subreq;
struct notify_change *changes;
size_t num_changes;
};
static void cli_smb2_notify_done(struct tevent_req *subreq);
static bool cli_smb2_notify_cancel(struct tevent_req *req);
struct tevent_req *cli_smb2_notify_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct cli_state *cli,
uint16_t fnum,
uint32_t buffer_size,
uint32_t completion_filter,
bool recursive)
{
struct tevent_req *req = NULL;
struct cli_smb2_notify_state *state = NULL;
struct smb2_hnd *ph = NULL;
NTSTATUS status;
req = tevent_req_create(mem_ctx, &state,
struct cli_smb2_notify_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);
}
status = map_fnum_to_smb2_handle(cli, fnum, &ph);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
state->subreq = smb2cli_notify_send(
state,
ev,
cli->conn,
cli->timeout,
cli->smb2.session,
cli->smb2.tcon,
buffer_size,
ph->fid_persistent,
ph->fid_volatile,
completion_filter,
recursive);
if (tevent_req_nomem(state->subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(state->subreq, cli_smb2_notify_done, req);
tevent_req_set_cancel_fn(req, cli_smb2_notify_cancel);
return req;
}
static bool cli_smb2_notify_cancel(struct tevent_req *req)
{
struct cli_smb2_notify_state *state = tevent_req_data(
req, struct cli_smb2_notify_state);
bool ok;
ok = tevent_req_cancel(state->subreq);
return ok;
}
static void cli_smb2_notify_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct cli_smb2_notify_state *state = tevent_req_data(
req, struct cli_smb2_notify_state);
uint8_t *base;
uint32_t len;
uint32_t ofs;
NTSTATUS status;
status = smb2cli_notify_recv(subreq, state, &base, &len);
TALLOC_FREE(subreq);
if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
tevent_req_done(req);
return;
}
if (tevent_req_nterror(req, status)) {
return;
}
ofs = 0;
while (len - ofs >= 12) {
struct notify_change *tmp;
struct notify_change *c;
uint32_t next_ofs = IVAL(base, ofs);
uint32_t file_name_length = IVAL(base, ofs+8);
size_t namelen;
bool ok;
tmp = talloc_realloc(
state,
state->changes,
struct notify_change,
state->num_changes + 1);
if (tevent_req_nomem(tmp, req)) {
return;
}
state->changes = tmp;
c = &state->changes[state->num_changes];
state->num_changes += 1;
if (smb_buffer_oob(len, ofs, next_ofs) ||
smb_buffer_oob(len, ofs+12, file_name_length)) {
tevent_req_nterror(
req, NT_STATUS_INVALID_NETWORK_RESPONSE);
return;
}
c->action = IVAL(base, ofs+4);
ok = convert_string_talloc(
state->changes,
CH_UTF16LE,
CH_UNIX,
base + ofs + 12,
file_name_length,
&c->name,
&namelen);
if (!ok) {
tevent_req_nterror(
req, NT_STATUS_INVALID_NETWORK_RESPONSE);
return;
}
if (next_ofs == 0) {
break;
}
ofs += next_ofs;
}
tevent_req_done(req);
}
NTSTATUS cli_smb2_notify_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
struct notify_change **pchanges,
uint32_t *pnum_changes)
{
struct cli_smb2_notify_state *state = tevent_req_data(
req, struct cli_smb2_notify_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
return status;
}
*pchanges = talloc_move(mem_ctx, &state->changes);
*pnum_changes = state->num_changes;
return NT_STATUS_OK;
}
NTSTATUS cli_smb2_notify(struct cli_state *cli, uint16_t fnum,
uint32_t buffer_size, uint32_t completion_filter,
bool recursive, TALLOC_CTX *mem_ctx,
struct notify_change **pchanges,
uint32_t *pnum_changes)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
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;
}
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = cli_smb2_notify_send(
frame,
ev,
cli,
fnum,
buffer_size,
completion_filter,
recursive);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = cli_smb2_notify_recv(req, mem_ctx, pchanges, pnum_changes);
fail:
TALLOC_FREE(frame);
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;
};
static void cli_smb2_get_reparse_point_fnum_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 *req, *subreq;
struct cli_smb2_get_reparse_point_fnum_state *state = NULL;
NTSTATUS status;
req = tevent_req_create(mem_ctx, &state,
struct cli_smb2_get_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);
}
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);
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);
return req;
}
static void cli_smb2_get_reparse_point_fnum_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;
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);
}
NTSTATUS cli_smb2_get_reparse_point_fnum_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
DATA_BLOB *output)
{
struct cli_smb2_get_reparse_point_fnum_state *state = tevent_req_data(
req, struct cli_smb2_get_reparse_point_fnum_state);
if (tevent_req_is_nterror(req, &state->cli->raw_status)) {
NTSTATUS status = state->cli->raw_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;
}
tevent_req_received(req);
return NT_STATUS_OK;
}