/* Unix SMB/CIFS implementation. Core SMB2 server Copyright (C) Stefan Metzmacher 2009 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 . */ #include "includes.h" #include "smbd/smbd.h" #include "smbd/globals.h" #include "../libcli/smb/smb_common.h" #include "../lib/util/tevent_ntstatus.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_SMB2 static struct tevent_req *smbd_smb2_close_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct smbd_smb2_request *smb2req, struct files_struct *in_fsp, uint16_t in_flags); static NTSTATUS smbd_smb2_close_recv(struct tevent_req *req, uint16_t *out_flags, struct timespec *out_creation_ts, struct timespec *out_last_access_ts, struct timespec *out_last_write_ts, struct timespec *out_change_ts, uint64_t *out_allocation_size, uint64_t *out_end_of_file, uint32_t *out_file_attributes); static void smbd_smb2_request_close_done(struct tevent_req *subreq); NTSTATUS smbd_smb2_request_process_close(struct smbd_smb2_request *req) { const uint8_t *inbody; uint16_t in_flags; uint64_t in_file_id_persistent; uint64_t in_file_id_volatile; struct files_struct *in_fsp; NTSTATUS status; struct tevent_req *subreq; status = smbd_smb2_request_verify_sizes(req, 0x18); if (!NT_STATUS_IS_OK(status)) { return smbd_smb2_request_error(req, status); } inbody = SMBD_SMB2_IN_BODY_PTR(req); in_flags = SVAL(inbody, 0x02); in_file_id_persistent = BVAL(inbody, 0x08); in_file_id_volatile = BVAL(inbody, 0x10); in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile); if (in_fsp == NULL) { return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED); } subreq = smbd_smb2_close_send(req, req->sconn->ev_ctx, req, in_fsp, in_flags); if (subreq == NULL) { return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY); } tevent_req_set_callback(subreq, smbd_smb2_request_close_done, req); return smbd_smb2_request_pending_queue(req, subreq, 500); } static void smbd_smb2_request_close_done(struct tevent_req *subreq) { struct smbd_smb2_request *req = tevent_req_callback_data(subreq, struct smbd_smb2_request); DATA_BLOB outbody; uint16_t out_flags = 0; connection_struct *conn = req->tcon->compat; struct timespec out_creation_ts = { 0, }; struct timespec out_last_access_ts = { 0, }; struct timespec out_last_write_ts = { 0, }; struct timespec out_change_ts = { 0, }; uint64_t out_allocation_size = 0; uint64_t out_end_of_file = 0; uint32_t out_file_attributes = 0; NTSTATUS status; NTSTATUS error; status = smbd_smb2_close_recv(subreq, &out_flags, &out_creation_ts, &out_last_access_ts, &out_last_write_ts, &out_change_ts, &out_allocation_size, &out_end_of_file, &out_file_attributes); TALLOC_FREE(subreq); if (!NT_STATUS_IS_OK(status)) { error = smbd_smb2_request_error(req, status); if (!NT_STATUS_IS_OK(error)) { smbd_server_connection_terminate(req->xconn, nt_errstr(error)); return; } return; } outbody = smbd_smb2_generate_outbody(req, 0x3C); if (outbody.data == NULL) { error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY); if (!NT_STATUS_IS_OK(error)) { smbd_server_connection_terminate(req->xconn, nt_errstr(error)); return; } return; } SSVAL(outbody.data, 0x00, 0x3C); /* struct size */ SSVAL(outbody.data, 0x02, out_flags); SIVAL(outbody.data, 0x04, 0); /* reserved */ put_long_date_full_timespec(conn->ts_res, (char *)outbody.data + 0x08, &out_creation_ts); put_long_date_full_timespec(conn->ts_res, (char *)outbody.data + 0x10, &out_last_access_ts); put_long_date_full_timespec(conn->ts_res, (char *)outbody.data + 0x18, &out_last_write_ts); put_long_date_full_timespec(conn->ts_res, (char *)outbody.data + 0x20, &out_change_ts); SBVAL(outbody.data, 0x28, out_allocation_size); SBVAL(outbody.data, 0x30, out_end_of_file); SIVAL(outbody.data, 0x38, out_file_attributes); error = smbd_smb2_request_done(req, outbody, NULL); if (!NT_STATUS_IS_OK(error)) { smbd_server_connection_terminate(req->xconn, nt_errstr(error)); return; } } static void setup_close_full_information(connection_struct *conn, struct smb_filename *smb_fname, struct timespec *out_creation_ts, struct timespec *out_last_access_ts, struct timespec *out_last_write_ts, struct timespec *out_change_ts, uint16_t *out_flags, uint64_t *out_allocation_size, uint64_t *out_end_of_file) { *out_flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION; *out_last_write_ts = smb_fname->st.st_ex_mtime; *out_last_access_ts = smb_fname->st.st_ex_atime; *out_creation_ts = get_create_timespec(conn, NULL, smb_fname); *out_change_ts = get_change_timespec(conn, NULL, smb_fname); if (lp_dos_filetime_resolution(SNUM(conn))) { dos_filetime_timespec(out_creation_ts); dos_filetime_timespec(out_last_write_ts); dos_filetime_timespec(out_last_access_ts); dos_filetime_timespec(out_change_ts); } if (!S_ISDIR(smb_fname->st.st_ex_mode)) { *out_end_of_file = get_file_size_stat(&smb_fname->st); } *out_allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, NULL, &smb_fname->st); } static NTSTATUS smbd_smb2_close(struct smbd_smb2_request *req, struct files_struct **_fsp, uint16_t in_flags, uint16_t *out_flags, struct timespec *out_creation_ts, struct timespec *out_last_access_ts, struct timespec *out_last_write_ts, struct timespec *out_change_ts, uint64_t *out_allocation_size, uint64_t *out_end_of_file, uint32_t *out_file_attributes) { NTSTATUS status; struct smb_request *smbreq; connection_struct *conn = req->tcon->compat; struct files_struct *fsp = *_fsp; struct smb_filename *smb_fname = NULL; *out_creation_ts = (struct timespec){0, SAMBA_UTIME_OMIT}; *out_last_access_ts = (struct timespec){0, SAMBA_UTIME_OMIT}; *out_last_write_ts = (struct timespec){0, SAMBA_UTIME_OMIT}; *out_change_ts = (struct timespec){0, SAMBA_UTIME_OMIT}; *out_flags = 0; *out_allocation_size = 0; *out_end_of_file = 0; *out_file_attributes = 0; DEBUG(10,("smbd_smb2_close: %s - %s\n", fsp_str_dbg(fsp), fsp_fnum_dbg(fsp))); smbreq = smbd_smb2_fake_smb_request(req, fsp); if (smbreq == NULL) { return NT_STATUS_NO_MEMORY; } if (in_flags & SMB2_CLOSE_FLAGS_FULL_INFORMATION) { *out_file_attributes = fdos_mode(fsp); fsp->fsp_flags.fstat_before_close = true; } status = close_file_smb(smbreq, fsp, NORMAL_CLOSE); if (!NT_STATUS_IS_OK(status)) { DEBUG(5,("smbd_smb2_close: close_file[%s]: %s\n", smb_fname_str_dbg(smb_fname), nt_errstr(status))); file_free(smbreq, fsp); *_fsp = fsp = NULL; return status; } if (in_flags & SMB2_CLOSE_FLAGS_FULL_INFORMATION) { setup_close_full_information(conn, fsp->fsp_name, out_creation_ts, out_last_access_ts, out_last_write_ts, out_change_ts, out_flags, out_allocation_size, out_end_of_file); } file_free(smbreq, fsp); *_fsp = fsp = NULL; return NT_STATUS_OK; } struct smbd_smb2_close_state { struct smbd_smb2_request *smb2req; struct files_struct *in_fsp; uint16_t in_flags; uint16_t out_flags; struct timespec out_creation_ts; struct timespec out_last_access_ts; struct timespec out_last_write_ts; struct timespec out_change_ts; uint64_t out_allocation_size; uint64_t out_end_of_file; uint32_t out_file_attributes; struct tevent_queue *wait_queue; }; static void smbd_smb2_close_wait_done(struct tevent_req *subreq); static struct tevent_req *smbd_smb2_close_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct smbd_smb2_request *smb2req, struct files_struct *in_fsp, uint16_t in_flags) { struct tevent_req *req; struct smbd_smb2_close_state *state; const char *fsp_name_str = NULL; const char *fsp_fnum_str = NULL; unsigned i; NTSTATUS status; if (CHECK_DEBUGLVL(DBGLVL_INFO)) { fsp_name_str = fsp_str_dbg(in_fsp); fsp_fnum_str = fsp_fnum_dbg(in_fsp); } DBG_DEBUG("%s - %s\n", fsp_name_str, fsp_fnum_str); req = tevent_req_create(mem_ctx, &state, struct smbd_smb2_close_state); if (req == NULL) { return NULL; } state->smb2req = smb2req; state->in_fsp = in_fsp; state->in_flags = in_flags; in_fsp->fsp_flags.closing = true; i = 0; while (i < in_fsp->num_aio_requests) { bool ok = tevent_req_cancel(in_fsp->aio_requests[i]); if (ok) { continue; } i += 1; } if (in_fsp->num_aio_requests != 0) { struct tevent_req *subreq; state->wait_queue = tevent_queue_create(state, "smbd_smb2_close_send_wait_queue"); if (tevent_req_nomem(state->wait_queue, req)) { return tevent_req_post(req, ev); } /* * Now wait until all aio requests on this fsp are * finished. * * We don't set a callback, as we just want to block the * wait queue and the talloc_free() of fsp->aio_request * will remove the item from the wait queue. */ subreq = tevent_queue_wait_send(in_fsp->aio_requests, smb2req->sconn->ev_ctx, state->wait_queue); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } /* * Now we add our own waiter to the end of the queue, * this way we get notified when all pending requests are * finished. */ subreq = tevent_queue_wait_send(state, smb2req->sconn->ev_ctx, state->wait_queue); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, smbd_smb2_close_wait_done, req); return req; } status = smbd_smb2_close(smb2req, &state->in_fsp, state->in_flags, &state->out_flags, &state->out_creation_ts, &state->out_last_access_ts, &state->out_last_write_ts, &state->out_change_ts, &state->out_allocation_size, &state->out_end_of_file, &state->out_file_attributes); if (tevent_req_nterror(req, status)) { DBG_INFO("%s - %s: close file failed: %s\n", fsp_name_str, fsp_fnum_str, nt_errstr(status)); return tevent_req_post(req, ev); } tevent_req_done(req); return tevent_req_post(req, ev); } static void smbd_smb2_close_wait_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct smbd_smb2_close_state *state = tevent_req_data( req, struct smbd_smb2_close_state); NTSTATUS status; tevent_queue_wait_recv(subreq); TALLOC_FREE(subreq); status = smbd_smb2_close(state->smb2req, &state->in_fsp, state->in_flags, &state->out_flags, &state->out_creation_ts, &state->out_last_access_ts, &state->out_last_write_ts, &state->out_change_ts, &state->out_allocation_size, &state->out_end_of_file, &state->out_file_attributes); if (tevent_req_nterror(req, status)) { return; } tevent_req_done(req); } static NTSTATUS smbd_smb2_close_recv(struct tevent_req *req, uint16_t *out_flags, struct timespec *out_creation_ts, struct timespec *out_last_access_ts, struct timespec *out_last_write_ts, struct timespec *out_change_ts, uint64_t *out_allocation_size, uint64_t *out_end_of_file, uint32_t *out_file_attributes) { struct smbd_smb2_close_state *state = tevent_req_data(req, struct smbd_smb2_close_state); NTSTATUS status; if (tevent_req_is_nterror(req, &status)) { tevent_req_received(req); return status; } *out_flags = state->out_flags; *out_creation_ts = state->out_creation_ts; *out_last_access_ts = state->out_last_access_ts; *out_last_write_ts = state->out_last_write_ts; *out_change_ts = state->out_change_ts; *out_allocation_size = state->out_allocation_size; *out_end_of_file = state->out_end_of_file; *out_file_attributes = state->out_file_attributes; tevent_req_received(req); return NT_STATUS_OK; }