/* 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 . */ /* 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; inum_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; 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; } /* * parse_finfo_id_both_directory_info() checks for overflow, * no need to check again here. */ 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 smb2_hnd *ph = NULL; 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 = map_fnum_to_smb2_handle(cli, fnum, &ph); 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; struct smb2_hnd *ph = NULL; uint16_t fnum = 0xffff; TALLOC_CTX *frame = talloc_stackframe(); if (smbXcli_conn_has_async_calls(cli->conn)) { /* * Can't use sync call while an async call is in flight */ status = NT_STATUS_INVALID_PARAMETER; goto fail; } if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) { status = NT_STATUS_INVALID_PARAMETER; goto fail; } status = get_fnum_from_path(cli, name, FILE_READ_ATTRIBUTES, &fnum); if (!NT_STATUS_IS_OK(status)) { goto fail; } status = map_fnum_to_smb2_handle(cli, fnum, &ph); 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; iout_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; }