/* 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" 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 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) { 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; struct smb2_create_blobs *cblobs = NULL; 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; NTSTATUS status; 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); cblobs = talloc_zero(state, struct smb2_create_blobs); if (tevent_req_nomem(cblobs, req)) { return tevent_req_post(req, ev); } status = smb2_create_blob_add(state, cblobs, SMB2_CREATE_TAG_TWRP, twrp_blob); 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, 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, NULL, NULL); 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) { 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; } 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, uint16_t *pfid, struct smb_create_returns *cr) { 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); if (req == NULL) { goto fail; } if (!tevent_req_poll_ntstatus(req, ev, &status)) { goto fail; } status = cli_smb2_create_fnum_recv(req, pfid, cr); 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_delete_on_close_state { struct cli_state *cli; uint16_t fnum; struct smb2_hnd *ph; 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; NTSTATUS status; req = tevent_req_create(mem_ctx, &state, struct cli_smb2_delete_on_close_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); } /* * 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 = smb2cli_set_info_send(state, ev, cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, in_info_type, in_file_info_class, &state->inbuf, /* in_input_buffer */ 0, /* in_additional_info */ 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_delete_on_close_done, req); return req; } static void cli_smb2_delete_on_close_done(struct tevent_req *subreq) { NTSTATUS status = smb2cli_set_info_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; } /*************************************************************** Small wrapper that allows SMB2 to create a directory Synchronous only. ***************************************************************/ NTSTATUS cli_smb2_mkdir(struct cli_state *cli, const char *dname) { NTSTATUS status; uint16_t fnum; 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; } status = cli_smb2_create_fnum(cli, dname, 0, /* create_flags */ SMB2_IMPERSONATION_IMPERSONATION, 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 */ &fnum, NULL); if (!NT_STATUS_IS_OK(status)) { return status; } return cli_smb2_close_fnum(cli, fnum); } /*************************************************************** Small wrapper that allows SMB2 to delete a directory Synchronous only. ***************************************************************/ NTSTATUS cli_smb2_rmdir(struct cli_state *cli, const char *dname) { NTSTATUS status; uint16_t fnum; 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; } status = cli_smb2_create_fnum(cli, 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 */ &fnum, NULL); 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. */ status = cli_smb2_create_fnum(cli, 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| FILE_DELETE_ON_CLOSE| FILE_OPEN_REPARSE_POINT, /* create_options */ &fnum, NULL); } if (!NT_STATUS_IS_OK(status)) { return status; } status = cli_smb2_delete_on_close(cli, fnum, true); if (!NT_STATUS_IS_OK(status)) { cli_smb2_close_fnum(cli, fnum); return status; } return cli_smb2_close_fnum(cli, fnum); } /*************************************************************** Small wrapper that allows SMB2 to unlink a pathname. Synchronous only. ***************************************************************/ NTSTATUS cli_smb2_unlink(struct cli_state *cli, const char *fname) { NTSTATUS status; uint16_t fnum; 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; } status = cli_smb2_create_fnum(cli, fname, 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 */ &fnum, NULL); 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. */ status = cli_smb2_create_fnum(cli, fname, 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 */ &fnum, NULL); } if (!NT_STATUS_IS_OK(status)) { return status; } return cli_smb2_close_fnum(cli, fnum); } /*************************************************************** Utility function to parse a SMB2_FIND_ID_BOTH_DIRECTORY_INFO reply. ***************************************************************/ static NTSTATUS parse_finfo_id_both_directory_info(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->mode = CVAL(dir_data + 56, 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; } 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; } /*************************************************************** Wrapper that allows SMB2 to list a directory. Synchronous only. ***************************************************************/ NTSTATUS cli_smb2_list(struct cli_state *cli, const char *pathname, uint16_t attribute, NTSTATUS (*fn)(const char *, struct file_info *, const char *, void *), void *state) { NTSTATUS status; uint16_t fnum = 0xffff; char *parent_dir = NULL; const char *mask = NULL; struct smb2_hnd *ph = NULL; bool processed_file = false; TALLOC_CTX *frame = talloc_stackframe(); TALLOC_CTX *subframe = NULL; bool mask_has_wild; uint32_t max_trans = smb2cli_conn_max_trans_size(cli->conn); 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; } /* Get the directory name. */ if (!windows_parent_dirname(frame, pathname, &parent_dir, &mask)) { status = NT_STATUS_NO_MEMORY; goto fail; } mask_has_wild = ms_has_wild(mask); status = cli_smb2_create_fnum(cli, parent_dir, 0, /* create_flags */ SMB2_IMPERSONATION_IMPERSONATION, SEC_DIR_LIST|SEC_DIR_READ_ATTRIBUTE,/* desired_access */ FILE_ATTRIBUTE_DIRECTORY, /* file attributes */ FILE_SHARE_READ|FILE_SHARE_WRITE, /* share_access */ FILE_OPEN, /* create_disposition */ FILE_DIRECTORY_FILE, /* create_options */ &fnum, NULL); 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; } do { uint8_t *dir_data = NULL; uint32_t dir_data_length = 0; uint32_t next_offset = 0; subframe = talloc_stackframe(); status = smb2cli_query_directory(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, SMB2_FIND_ID_BOTH_DIRECTORY_INFO, 0, /* flags */ 0, /* file_index */ ph->fid_persistent, ph->fid_volatile, mask, max_trans, subframe, &dir_data, &dir_data_length); if (!NT_STATUS_IS_OK(status)) { if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) { break; } goto fail; } do { struct file_info *finfo = talloc_zero(subframe, struct file_info); if (finfo == NULL) { status = NT_STATUS_NO_MEMORY; goto fail; } status = parse_finfo_id_both_directory_info(dir_data, dir_data_length, finfo, &next_offset); if (!NT_STATUS_IS_OK(status)) { goto fail; } if (dir_check_ftype((uint32_t)finfo->mode, (uint32_t)attribute)) { /* * Only process if attributes match. * On SMB1 server does this, so on * SMB2 we need to emulate in the * client. * * https://bugzilla.samba.org/show_bug.cgi?id=10260 */ processed_file = true; status = fn(cli->dfs_mountpoint, finfo, pathname, state); if (!NT_STATUS_IS_OK(status)) { break; } } TALLOC_FREE(finfo); /* Move to next entry. */ if (next_offset) { dir_data += next_offset; dir_data_length -= next_offset; } } while (next_offset != 0); TALLOC_FREE(subframe); if (!mask_has_wild) { /* * MacOSX 10 doesn't set STATUS_NO_MORE_FILES * when handed a non-wildcard path. Do it * for the server (with a non-wildcard path * there should only ever be one file returned. */ status = STATUS_NO_MORE_FILES; break; } } while (NT_STATUS_IS_OK(status)); if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) { status = NT_STATUS_OK; } if (NT_STATUS_IS_OK(status) && !processed_file) { /* * In SMB1 findfirst returns NT_STATUS_NO_SUCH_FILE * if no files match. Emulate this in the client. */ status = NT_STATUS_NO_SUCH_FILE; } fail: if (fnum != 0xffff) { cli_smb2_close_fnum(cli, fnum); } cli->raw_status = status; TALLOC_FREE(subframe); TALLOC_FREE(frame); 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_strdup(talloc_tos(), name); modname[namelen-1] = '\0'; 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 */ &fnum, &cr); 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 */ &fnum, &cr); } 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; } /*************************************************************** Wrapper that allows SMB2 to check if a path is a directory. Synchronous only. ***************************************************************/ NTSTATUS cli_smb2_chkpath(struct cli_state *cli, const char *name) { 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 */ return NT_STATUS_INVALID_PARAMETER; } if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) { return NT_STATUS_INVALID_PARAMETER; } /* Ensure this is a directory. */ 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 */ &fnum, NULL); if (!NT_STATUS_IS_OK(status)) { return status; } return cli_smb2_close_fnum(cli, fnum); } /*************************************************************** Helper function for pathname operations. ***************************************************************/ static NTSTATUS get_fnum_from_path(struct cli_state *cli, const char *name, uint32_t desired_access, uint16_t *pfnum) { NTSTATUS status; size_t namelen = strlen(name); TALLOC_CTX *frame = talloc_stackframe(); uint32_t create_options = 0; /* SMB2 is pickier about pathnames. Ensure it doesn't end in a '\' */ if (namelen > 0 && name[namelen-1] == '\\') { char *modname = talloc_strdup(frame, name); if (modname == NULL) { status = NT_STATUS_NO_MEMORY; goto fail; } modname[namelen-1] = '\0'; name = modname; } /* Try to open a file handle first. */ status = cli_smb2_create_fnum(cli, name, 0, /* create_flags */ SMB2_IMPERSONATION_IMPERSONATION, desired_access, 0, /* file attributes */ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */ FILE_OPEN, /* create_disposition */ create_options, pfnum, NULL); 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. */ create_options |= FILE_OPEN_REPARSE_POINT; status = cli_smb2_create_fnum(cli, name, 0, /* create_flags */ SMB2_IMPERSONATION_IMPERSONATION, desired_access, 0, /* file attributes */ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */ FILE_OPEN, /* create_disposition */ create_options, pfnum, NULL); } if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { create_options |= FILE_DIRECTORY_FILE; status = cli_smb2_create_fnum(cli, name, 0, /* create_flags */ SMB2_IMPERSONATION_IMPERSONATION, desired_access, FILE_ATTRIBUTE_DIRECTORY, /* file attributes */ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */ FILE_OPEN, /* create_disposition */ FILE_DIRECTORY_FILE, /* create_options */ pfnum, NULL); } 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; struct smb2_hnd *ph = NULL; 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 = map_fnum_to_smb2_handle(cli, fnum, &ph); if (!NT_STATUS_IS_OK(status)) { goto fail; } /* getinfo on the handle with info_type SMB2_GETINFO_FILE (1), level SMB_FILE_ALTERNATE_NAME_INFORMATION (1021) == SMB2 21 */ status = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 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 */ ph->fid_persistent, ph->fid_volatile, 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 query a fnum info (basic level). Synchronous only. ***************************************************************/ NTSTATUS cli_smb2_qfileinfo_basic(struct cli_state *cli, uint16_t fnum, uint16_t *mode, off_t *size, struct timespec *create_time, struct timespec *access_time, struct timespec *write_time, struct timespec *change_time, SMB_INO_T *ino) { NTSTATUS status; DATA_BLOB outbuf = data_blob_null; struct smb2_hnd *ph = 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 = map_fnum_to_smb2_handle(cli, fnum, &ph); if (!NT_STATUS_IS_OK(status)) { goto fail; } /* getinfo on the handle with info_type SMB2_GETINFO_FILE (1), level 0x12 (SMB2_FILE_ALL_INFORMATION). */ status = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 1, /* in_info_type */ (SMB_FILE_ALL_INFORMATION - 1000), /* in_file_info_class */ 0xFFFF, /* in_max_output_length */ NULL, /* in_input_buffer */ 0, /* in_additional_info */ 0, /* in_flags */ ph->fid_persistent, ph->fid_volatile, frame, &outbuf); if (!NT_STATUS_IS_OK(status)) { goto fail; } /* Parse the reply. */ if (outbuf.length < 0x60) { status = NT_STATUS_INVALID_NETWORK_RESPONSE; goto fail; } if (create_time) { *create_time = interpret_long_date((const char *)outbuf.data + 0x0); } if (access_time) { *access_time = interpret_long_date((const char *)outbuf.data + 0x8); } if (write_time) { *write_time = interpret_long_date((const char *)outbuf.data + 0x10); } if (change_time) { *change_time = interpret_long_date((const char *)outbuf.data + 0x18); } if (mode) { uint32_t attr = IVAL(outbuf.data, 0x20); *mode = (uint16_t)attr; } if (size) { uint64_t file_size = BVAL(outbuf.data, 0x30); *size = (off_t)file_size; } if (ino) { uint64_t file_index = BVAL(outbuf.data, 0x40); *ino = (SMB_INO_T)file_index; } fail: cli->raw_status = status; TALLOC_FREE(frame); return status; } /*************************************************************** Wrapper that allows SMB2 to query an fnum. Implement on top of cli_smb2_qfileinfo_basic(). Synchronous only. ***************************************************************/ NTSTATUS cli_smb2_getattrE(struct cli_state *cli, uint16_t fnum, uint16_t *attr, off_t *size, time_t *change_time, time_t *access_time, time_t *write_time) { struct timespec access_time_ts; struct timespec write_time_ts; struct timespec change_time_ts; NTSTATUS status = cli_smb2_qfileinfo_basic(cli, fnum, attr, size, NULL, &access_time_ts, &write_time_ts, &change_time_ts, NULL); cli->raw_status = status; if (!NT_STATUS_IS_OK(status)) { return status; } if (change_time) { *change_time = change_time_ts.tv_sec; } if (access_time) { *access_time = access_time_ts.tv_sec; } if (write_time) { *write_time = write_time_ts.tv_sec; } return NT_STATUS_OK; } /*************************************************************** Wrapper that allows SMB2 to get pathname attributes. Synchronous only. ***************************************************************/ NTSTATUS cli_smb2_getatr(struct cli_state *cli, const char *name, uint16_t *attr, off_t *size, time_t *write_time) { NTSTATUS status; uint16_t fnum = 0xffff; struct smb2_hnd *ph = 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; } status = map_fnum_to_smb2_handle(cli, fnum, &ph); if (!NT_STATUS_IS_OK(status)) { goto fail; } status = cli_smb2_getattrE(cli, fnum, attr, size, NULL, NULL, write_time); if (!NT_STATUS_IS_OK(status)) { 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 query a pathname info (basic level). Implement on top of cli_smb2_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, uint16_t *mode, 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_smb2_qfileinfo_basic(cli, fnum, mode, 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; struct smb2_hnd *ph = NULL; 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; } status = map_fnum_to_smb2_handle(cli, fnum, &ph); 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 = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 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 */ ph->fid_persistent, ph->fid_volatile, 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; struct smb2_hnd *ph = 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_WRITE_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 = smb2cli_set_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, in_info_type, in_file_info_class, p_in_data, /* in_input_buffer */ 0, /* in_additional_info */ ph->fid_persistent, ph->fid_volatile); 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, uint16_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; } SSVAL(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) { NTSTATUS status; struct smb2_hnd *ph = NULL; 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; } status = map_fnum_to_smb2_handle(cli, fnum, &ph); if (!NT_STATUS_IS_OK(status)) { return status; } /* 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 = smb2cli_set_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 1, /* in_info_type */ SMB_FILE_BASIC_INFORMATION - 1000, /* in_file_info_class */ &inbuf, /* in_input_buffer */ 0, /* in_additional_info */ ph->fid_persistent, ph->fid_volatile); 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; struct smb2_hnd *ph = 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 */ &fnum, NULL); 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; } /* getinfo on the returned handle with info_type SMB2_GETINFO_FS (2), level 3 (SMB_FS_SIZE_INFORMATION). */ status = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 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 */ ph->fid_persistent, ph->fid_volatile, 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; struct smb2_hnd *ph = 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 */ &fnum, NULL); 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; } /* getinfo on the returned handle with info_type SMB2_GETINFO_FS (2), level 7 (SMB_FS_FULL_SIZE_INFORMATION). */ status = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, SMB2_GETINFO_FS, /* in_info_type */ /* in_file_info_class */ SMB_FS_FULL_SIZE_INFORMATION - 1000, 0xFFFF, /* in_max_output_length */ NULL, /* in_input_buffer */ 0, /* in_additional_info */ 0, /* in_flags */ ph->fid_persistent, ph->fid_volatile, 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; struct smb2_hnd *ph = 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 */ &fnum, NULL); 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 = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 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 */ ph->fid_persistent, ph->fid_volatile, 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; struct smb2_hnd *ph = 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 */ &fnum, NULL); 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; } /* getinfo on the returned handle with info_type SMB2_GETINFO_FS (2), level 1 (SMB_FS_VOLUME_INFORMATION). */ status = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, SMB2_GETINFO_FS, /* 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 */ ph->fid_persistent, ph->fid_volatile, 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; } clistr_pull_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; } /*************************************************************** Wrapper that allows SMB2 to query a security descriptor. Synchronous only. ***************************************************************/ NTSTATUS cli_smb2_query_security_descriptor(struct cli_state *cli, uint16_t fnum, uint32_t sec_info, TALLOC_CTX *mem_ctx, struct security_descriptor **ppsd) { NTSTATUS status; DATA_BLOB outbuf = data_blob_null; struct smb2_hnd *ph = NULL; struct security_descriptor *lsd = NULL; TALLOC_CTX *frame = talloc_stackframe(); if (smbXcli_conn_has_async_calls(cli->conn)) { /* * Can't use sync call while an async call is in flight */ status = NT_STATUS_INVALID_PARAMETER; goto fail; } if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) { status = NT_STATUS_INVALID_PARAMETER; goto fail; } status = map_fnum_to_smb2_handle(cli, fnum, &ph); if (!NT_STATUS_IS_OK(status)) { goto fail; } /* getinfo on the returned handle with info_type SMB2_GETINFO_SEC (3) */ status = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 3, /* in_info_type */ 0, /* in_file_info_class */ 0xFFFF, /* in_max_output_length */ NULL, /* in_input_buffer */ sec_info, /* in_additional_info */ 0, /* in_flags */ ph->fid_persistent, ph->fid_volatile, frame, &outbuf); if (!NT_STATUS_IS_OK(status)) { goto fail; } /* Parse the reply. */ status = unmarshall_sec_desc(mem_ctx, outbuf.data, outbuf.length, &lsd); if (!NT_STATUS_IS_OK(status)) { goto fail; } if (ppsd != NULL) { *ppsd = lsd; } else { TALLOC_FREE(lsd); } fail: cli->raw_status = status; TALLOC_FREE(frame); return status; } /*************************************************************** Wrapper that allows SMB2 to set a security descriptor. Synchronous only. ***************************************************************/ NTSTATUS cli_smb2_set_security_descriptor(struct cli_state *cli, uint16_t fnum, uint32_t sec_info, const struct security_descriptor *sd) { NTSTATUS status; DATA_BLOB inbuf = data_blob_null; struct smb2_hnd *ph = 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 = map_fnum_to_smb2_handle(cli, fnum, &ph); if (!NT_STATUS_IS_OK(status)) { goto fail; } status = marshall_sec_desc(frame, sd, &inbuf.data, &inbuf.length); if (!NT_STATUS_IS_OK(status)) { goto fail; } /* setinfo on the returned handle with info_type SMB2_SETINFO_SEC (3) */ status = smb2cli_set_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 3, /* in_info_type */ 0, /* in_file_info_class */ &inbuf, /* in_input_buffer */ sec_info, /* in_additional_info */ ph->fid_persistent, ph->fid_volatile); fail: cli->raw_status = status; TALLOC_FREE(frame); return status; } /*************************************************************** Wrapper that allows SMB2 to rename a file. Synchronous only. ***************************************************************/ NTSTATUS cli_smb2_rename(struct cli_state *cli, const char *fname_src, const char *fname_dst, bool replace) { NTSTATUS status; DATA_BLOB inbuf = data_blob_null; uint16_t fnum = 0xffff; struct smb2_hnd *ph = NULL; smb_ucs2_t *converted_str = NULL; size_t converted_size_bytes = 0; 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; } status = get_fnum_from_path(cli, fname_src, DELETE_ACCESS, &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; } /* 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 '\' */ namelen = strlen(fname_dst); if (namelen > 0 && fname_dst[namelen-1] == '\\') { char *modname = talloc_strdup(frame, fname_dst); modname[namelen-1] = '\0'; fname_dst = modname; } if (!push_ucs2_talloc(frame, &converted_str, fname_dst, &converted_size_bytes)) { status = NT_STATUS_INVALID_PARAMETER; goto fail; } /* 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) { status = NT_STATUS_INVALID_PARAMETER; goto fail; } converted_size_bytes -= 2; inbuf = data_blob_talloc_zero(frame, 20 + converted_size_bytes); if (inbuf.data == NULL) { status = NT_STATUS_NO_MEMORY; goto fail; } if (replace) { SCVAL(inbuf.data, 0, 1); } SIVAL(inbuf.data, 16, converted_size_bytes); memcpy(inbuf.data + 20, converted_str, converted_size_bytes); /* setinfo on the returned handle with info_type SMB2_GETINFO_FILE (1), level SMB2_FILE_RENAME_INFORMATION (SMB_FILE_RENAME_INFORMATION - 1000) */ status = smb2cli_set_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 1, /* in_info_type */ SMB_FILE_RENAME_INFORMATION - 1000, /* in_file_info_class */ &inbuf, /* in_input_buffer */ 0, /* in_additional_info */ ph->fid_persistent, ph->fid_volatile); 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 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; struct smb2_hnd *ph = 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 = map_fnum_to_smb2_handle(cli, fnum, &ph); if (!NT_STATUS_IS_OK(status)) { 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 = smb2cli_set_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 1, /* in_info_type */ SMB_FILE_FULL_EA_INFORMATION - 1000, /* in_file_info_class */ &inbuf, /* in_input_buffer */ 0, /* in_additional_info */ ph->fid_persistent, ph->fid_volatile); 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 smb2_hnd *ph = 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; } status = map_fnum_to_smb2_handle(cli, fnum, &ph); 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 = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 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 */ ph->fid_persistent, ph->fid_volatile, 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; struct smb2_hnd *ph = 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; } status = map_fnum_to_smb2_handle(cli, quota_fnum, &ph); if (!NT_STATUS_IS_OK(status)) { 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 = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 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 */ ph->fid_persistent, ph->fid_volatile, 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; struct smb2_hnd *ph = 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; } status = map_fnum_to_smb2_handle(cli, quota_fnum, &ph); if (!NT_STATUS_IS_OK(status)) { 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 = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 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 */ ph->fid_persistent, ph->fid_volatile, 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; struct smb2_hnd *ph = 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 = map_fnum_to_smb2_handle(cli, quota_fnum, &ph); if (!NT_STATUS_IS_OK(status)) { goto cleanup; } status = smb2cli_query_info( cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 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 */ ph->fid_persistent, ph->fid_volatile, 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; struct smb2_hnd *ph = 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 = map_fnum_to_smb2_handle(cli, quota_fnum, &ph); if (!NT_STATUS_IS_OK(status)) { goto cleanup; } status = build_user_quota_buffer(qtl, 0, talloc_tos(), &inbuf, NULL); if (!NT_STATUS_IS_OK(status)) { goto cleanup; } status = smb2cli_set_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 4, /* in_info_type */ 0, /* in_file_info_class */ &inbuf, /* in_input_buffer */ 0, /* in_additional_info */ ph->fid_persistent, ph->fid_volatile); 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; struct smb2_hnd *ph = 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 = map_fnum_to_smb2_handle(cli, quota_fnum, &ph); if (!NT_STATUS_IS_OK(status)) { goto cleanup; } status = build_fs_quota_buffer(talloc_tos(), pqt, &inbuf, 0); if (!NT_STATUS_IS_OK(status)) { goto cleanup; } status = smb2cli_set_info( cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 2, /* in_info_type */ SMB_FS_QUOTA_INFORMATION - 1000, /* in_file_info_class */ &inbuf, /* in_input_buffer */ 0, /* in_additional_info */ ph->fid_persistent, ph->fid_volatile); 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); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; } tevent_req_done(req); } 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; DATA_BLOB inbuf = data_blob_null; struct smb2_hnd *ph = 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 = map_fnum_to_smb2_handle(cli, fnum, &ph); if (!NT_STATUS_IS_OK(status)) { goto fail; } inbuf = data_blob_talloc_zero(frame, 8); if (inbuf.data == NULL) { status = NT_STATUS_NO_MEMORY; goto fail; } SBVAL(inbuf.data, 0, newsize); /* setinfo on the handle with info_type SMB2_SETINFO_FILE (1), level 20 (SMB_FILE_END_OF_FILE_INFORMATION - 1000). */ status = smb2cli_set_info(cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon, 1, /* in_info_type */ /* in_file_info_class */ SMB_FILE_END_OF_FILE_INFORMATION - 1000, &inbuf, /* in_input_buffer */ 0, /* in_additional_info */ ph->fid_persistent, ph->fid_volatile); 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_set_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); NTSTATUS status; status = smb2cli_ioctl_recv(subreq, state, NULL, &state->output_buffer); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { state->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)) { tevent_req_received(req); return state->cli->raw_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; }