From: Ralph Boehme Date: Thu, 20 Jul 2017 14:14:31 +0000 (+0200) Subject: s3/smbd: factor out smbd_smb2_create_before_exec from smbd_smb2_create_send X-Git-Tag: tevent-0.9.34~117 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6478a2b1fd983888f322facd454dd6601dd61bf2;p=thirdparty%2Fsamba.git s3/smbd: factor out smbd_smb2_create_before_exec from smbd_smb2_create_send No change in behaviour, best viewed with: $ git show --diff-algorithm=histogram COMMIT Signed-off-by: Ralph Boehme Reviewed-by: Stefan Metzmacher --- diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c index 7ccdc5396c6..315442ed7f5 100644 --- a/source3/smbd/smb2_create.c +++ b/source3/smbd/smb2_create.c @@ -603,6 +603,7 @@ static NTSTATUS smbd_smb2_create_fetch_create_ctx( return NT_STATUS_OK; } +static void smbd_smb2_create_before_exec(struct tevent_req *req); static void smbd_smb2_create_finish(struct tevent_req *req); static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, @@ -737,666 +738,681 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, return req; } - if (state->exta != NULL) { - if (!lp_ea_support(SNUM(smb2req->tcon->compat))) { - tevent_req_nterror(req, - NT_STATUS_EAS_NOT_SUPPORTED); - return tevent_req_post(req, state->ev); - } + smbd_smb2_create_before_exec(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, state->ev); + } - state->ea_list = read_nttrans_ea_list( - state, - (const char *)state->exta->data.data, - state->exta->data.length); - if (state->ea_list == NULL) { - DEBUG(10,("smbd_smb2_create_send: read_ea_name_list failed.\n")); - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); - return tevent_req_post(req, state->ev); - } + DEBUG(10, ("smbd_smb2_create_send: open execution phase\n")); - /* - * NB. When SMB2+ unix extensions are added, - * we need to relax this check in invalid - * names - we used to not do this if - * lp_posix_pathnames() was false. - */ - if (ea_list_has_invalid_name(state->ea_list)) { - tevent_req_nterror(req, STATUS_INVALID_EA_NAME); - return tevent_req_post(req, state->ev); - } - } + /* + * For the backend file open procedure, there are + * three possible modes: replay operation (in which case + * there is nothing else to do), durable_reconnect or + * new open. + */ + if (state->replay_operation) { + state->result = state->op->compat; + state->result->op = state->op; + state->update_open = false; + state->info = state->op->create_action; + } else if (state->do_durable_reconnect) { + DATA_BLOB new_cookie = data_blob_null; + NTTIME now = timeval_to_nttime(&smb2req->request_time); - if (state->mxac != NULL) { - if (state->mxac->data.length == 0) { - state->max_access_time = 0; - } else if (state->mxac->data.length == 8) { - state->max_access_time = BVAL(state->mxac->data.data, 0); - } else { - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + status = smb2srv_open_recreate(smb2req->xconn, + smb1req->conn->session_info, + state->persistent_id, + state->create_guid, + now, + &state->op); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("smbd_smb2_create_send: " + "smb2srv_open_recreate failed: %s\n", + nt_errstr(status))); + tevent_req_nterror(req, status); return tevent_req_post(req, state->ev); } - } - if (state->secd != NULL) { - enum ndr_err_code ndr_err; + DEBUG(10, ("smb2_create_send: %s to recreate the " + "smb2srv_open struct for a durable handle.\n", + state->op->global->durable ? "succeeded" : "failed")); - state->sec_desc = talloc_zero(state, struct security_descriptor); - if (tevent_req_nomem(state->sec_desc, req)) { + if (!state->op->global->durable) { + talloc_free(state->op); + tevent_req_nterror(req, + NT_STATUS_OBJECT_NAME_NOT_FOUND); return tevent_req_post(req, state->ev); } - ndr_err = ndr_pull_struct_blob(&state->secd->data, - state->sec_desc, state->sec_desc, - (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); - if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { - DEBUG(2,("ndr_pull_security_descriptor failed: %s\n", - ndr_errstr(ndr_err))); - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); - return tevent_req_post(req, state->ev); - } - } + status = SMB_VFS_DURABLE_RECONNECT(smb1req->conn, + smb1req, + state->op, /* smbXsrv_open input */ + state->op->global->backend_cookie, + state->op, /* TALLOC_CTX */ + &state->result, + &new_cookie); + if (!NT_STATUS_IS_OK(status)) { + NTSTATUS return_status; - if (state->dhnq != NULL) { - if (state->dhnq->data.length != 16) { - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + + DEBUG(3, ("smbd_smb2_create_send: " + "durable_reconnect failed: %s => %s\n", + nt_errstr(status), + nt_errstr(return_status))); + + tevent_req_nterror(req, return_status); return tevent_req_post(req, state->ev); } - if (state->dh2q != NULL) { - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + DEBUG(10, ("result->oplock_type=%u, lease_ptr==%p\n", + (unsigned)state->result->oplock_type, state->lease_ptr)); + + status = smbd_smb2_create_durable_lease_check( + smb1req, state->fname, state->result, state->lease_ptr); + if (!NT_STATUS_IS_OK(status)) { + close_file(smb1req, state->result, SHUTDOWN_CLOSE); + tevent_req_nterror(req, status); return tevent_req_post(req, state->ev); } - /* - * durable handle request is processed below. - */ - state->durable_requested = true; - /* - * Set the timeout to 16 mins. - * - * TODO: test this against Windows 2012 - * as the default for durable v2 is 1 min. - */ - state->durable_timeout_msec = (16*60*1000); - } + data_blob_free(&state->op->global->backend_cookie); + state->op->global->backend_cookie = new_cookie; - if (state->dh2q != NULL) { - const uint8_t *p = state->dh2q->data.data; - uint32_t durable_v2_timeout = 0; - DATA_BLOB create_guid_blob; - const uint8_t *hdr; - uint32_t flags; + state->op->status = NT_STATUS_OK; + state->op->global->disconnect_time = 0; - if (state->dh2q->data.length != 32) { - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); - return tevent_req_post(req, state->ev); - } + /* save the timout for later update */ + state->durable_timeout_msec = state->op->global->durable_timeout_msec; - if (state->dhnq != NULL) { - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); - return tevent_req_post(req, state->ev); - } + state->update_open = true; - durable_v2_timeout = IVAL(p, 0); - create_guid_blob = data_blob_const(p + 16, 16); + state->info = FILE_WAS_OPENED; + } else { + struct smb_filename *smb_fname = NULL; + uint32_t ucf_flags; - status = GUID_from_ndr_blob(&create_guid_blob, - &state->_create_guid); - if (tevent_req_nterror(req, status)) { - return tevent_req_post(req, state->ev); + if (state->requested_oplock_level == SMB2_OPLOCK_LEVEL_LEASE) { + if (state->lease_ptr == NULL) { + state->requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE; + } + } else { + state->lease_ptr = NULL; } - state->create_guid = &state->_create_guid; /* - * we need to store the create_guid later + * For a DFS path the function parse_dfs_path() + * will do the path processing. */ - state->update_open = true; - /* - * And we need to create a cache for replaying the - * create. - */ - state->need_replay_cache = true; + if (!(smb1req->flags2 & FLAGS2_DFS_PATHNAMES)) { + /* convert '\\' into '/' */ + status = check_path_syntax(state->fname); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, state->ev); + } + } - /* - * durable handle v2 request processed below - */ - state->durable_requested = true; - state->durable_timeout_msec = durable_v2_timeout; - if (state->durable_timeout_msec == 0) { - /* - * Set the timeout to 1 min as default. - * - * This matches Windows 2012. - */ - state->durable_timeout_msec = (60*1000); + ucf_flags = filename_create_ucf_flags( + smb1req, state->in_create_disposition); + status = filename_convert(req, + smb1req->conn, + state->fname, + ucf_flags, + NULL, /* ppath_contains_wcards */ + &smb_fname); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, state->ev); } /* - * Check for replay operation. - * Only consider it when we have dh2q. - * If we do not have a replay operation, verify that - * the create_guid is not cached for replay. + * MS-SMB2: 2.2.13 SMB2 CREATE Request + * ImpersonationLevel ... MUST contain one of the + * following values. The server MUST validate this + * field, but otherwise ignore it. + * + * NB. The source4/torture/smb2/durable_open.c test + * shows this check is only done on real opens, not + * on durable handle-reopens. */ - hdr = SMBD_SMB2_IN_HDR_PTR(smb2req); - flags = IVAL(hdr, SMB2_HDR_FLAGS); - state->replay_operation = - flags & SMB2_HDR_FLAG_REPLAY_OPERATION; - status = smb2srv_open_lookup_replay_cache(smb2req->xconn, - state->create_guid, - 0 /* now */, - &state->op); - if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { - state->replay_operation = false; - } else if (tevent_req_nterror(req, status)) { - DBG_WARNING("smb2srv_open_lookup_replay_cache " - "failed: %s\n", nt_errstr(status)); + if (in_impersonation_level > + SMB2_IMPERSONATION_DELEGATE) { + tevent_req_nterror(req, + NT_STATUS_BAD_IMPERSONATION_LEVEL); return tevent_req_post(req, state->ev); - } else if (!state->replay_operation) { - /* - * If a create without replay operation flag - * is sent but with a create_guid that is - * currently in the replay cache -- fail. - */ - status = NT_STATUS_DUPLICATE_OBJECTID; - (void)tevent_req_nterror(req, status); + } + + /* + * We know we're going to do a local open, so now + * we must be protocol strict. JRA. + * + * MS-SMB2: 3.3.5.9 - Receiving an SMB2 CREATE Request + * If the file name length is greater than zero and the + * first character is a path separator character, the + * server MUST fail the request with + * STATUS_INVALID_PARAMETER. + */ + if (in_name[0] == '\\' || in_name[0] == '/') { + tevent_req_nterror(req, + NT_STATUS_INVALID_PARAMETER); return tevent_req_post(req, state->ev); } - } - if (state->dhnc != NULL) { - state->persistent_id = BVAL(state->dhnc->data.data, 0); - state->do_durable_reconnect = true; + status = SMB_VFS_CREATE_FILE(smb1req->conn, + smb1req, + 0, /* root_dir_fid */ + smb_fname, + in_desired_access, + in_share_access, + state->in_create_disposition, + in_create_options, + in_file_attributes, + map_smb2_oplock_levels_to_samba( + state->requested_oplock_level), + state->lease_ptr, + state->allocation_size, + 0, /* private_flags */ + state->sec_desc, + state->ea_list, + &state->result, + &state->info, + &in_context_blobs, + state->out_context_blobs); + if (!NT_STATUS_IS_OK(status)) { + if (open_was_deferred(smb1req->xconn, smb1req->mid)) { + SMBPROFILE_IOBYTES_ASYNC_SET_IDLE(smb2req->profile); + return req; + } + tevent_req_nterror(req, status); + return tevent_req_post(req, state->ev); + } + state->op = state->result->op; } - if (state->dh2c != NULL) { - const uint8_t *p = state->dh2c->data.data; - DATA_BLOB create_guid_blob; + /* + * here we have op == state->result->op + */ - state->persistent_id = BVAL(p, 0); - create_guid_blob = data_blob_const(p + 16, 16); + DEBUG(10, ("smbd_smb2_create_send: " + "response construction phase\n")); - status = GUID_from_ndr_blob(&create_guid_blob, - &state->_create_guid); - if (tevent_req_nterror(req, status)) { - return tevent_req_post(req, state->ev); + if (state->mxac != NULL) { + NTTIME last_write_time; + + last_write_time = unix_timespec_to_nt_time( + state->result->fsp_name->st.st_ex_mtime); + if (last_write_time != state->max_access_time) { + uint8_t p[8]; + uint32_t max_access_granted; + DATA_BLOB blob = data_blob_const(p, sizeof(p)); + + status = smbd_calculate_access_mask(smb1req->conn, + state->result->fsp_name, + false, + SEC_FLAG_MAXIMUM_ALLOWED, + &max_access_granted); + + SIVAL(p, 0, NT_STATUS_V(status)); + SIVAL(p, 4, max_access_granted); + + status = smb2_create_blob_add( + state->out_context_blobs, + state->out_context_blobs, + SMB2_CREATE_TAG_MXAC, + blob); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, state->ev); + } } + } - state->create_guid = &state->_create_guid; - state->do_durable_reconnect = true; + if (!state->replay_operation && state->durable_requested && + (fsp_lease_type(state->result) & SMB2_LEASE_HANDLE)) + { + status = SMB_VFS_DURABLE_COOKIE( + state->result, + state->op, + &state->op->global->backend_cookie); + if (!NT_STATUS_IS_OK(status)) { + state->op->global->backend_cookie = data_blob_null; + } + } + if (!state->replay_operation && state->op->global->backend_cookie.length > 0) + { + state->update_open = true; + + state->op->global->durable = true; + state->op->global->durable_timeout_msec = state->durable_timeout_msec; } - if (state->alsi != NULL) { - if (state->alsi->data.length != 8) { - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + if (state->update_open) { + state->op->global->create_guid = state->_create_guid; + if (state->need_replay_cache) { + state->op->flags |= SMBXSRV_OPEN_NEED_REPLAY_CACHE; + } + + status = smbXsrv_open_update(state->op); + DEBUG(10, ("smb2_create_send: smbXsrv_open_update " + "returned %s\n", + nt_errstr(status))); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); return tevent_req_post(req, state->ev); } - state->allocation_size = BVAL(state->alsi->data.data, 0); } - if (state->twrp != NULL) { - NTTIME nttime; - time_t t; - struct tm *tm; - char *tmpname = state->fname; + if (state->dhnq != NULL && state->op->global->durable) { + uint8_t p[8] = { 0, }; + DATA_BLOB blob = data_blob_const(p, sizeof(p)); - if (state->twrp->data.length != 8) { - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + status = smb2_create_blob_add(state->out_context_blobs, + state->out_context_blobs, + SMB2_CREATE_TAG_DHNQ, + blob); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); return tevent_req_post(req, state->ev); } + } - nttime = BVAL(state->twrp->data.data, 0); - t = nt_time_to_unix(nttime); - tm = gmtime(&t); + if (state->dh2q != NULL && state->op->global->durable && + /* + * For replay operations, we return the dh2q blob + * in the case of oplocks not based on the state of + * the open, but on whether it could have been granted + * for the request data. In the case of leases instead, + * the state of the open is used... + */ + (!state->replay_operation || + state->in_oplock_level == SMB2_OPLOCK_LEVEL_BATCH || + state->in_oplock_level == SMB2_OPLOCK_LEVEL_LEASE)) + { + uint8_t p[8] = { 0, }; + DATA_BLOB blob = data_blob_const(p, sizeof(p)); + uint32_t durable_v2_response_flags = 0; - state->fname = talloc_asprintf( - state, - "%s\\@GMT-%04u.%02u.%02u-%02u.%02u.%02u", - state->fname, - tm->tm_year + 1900, - tm->tm_mon + 1, - tm->tm_mday, - tm->tm_hour, - tm->tm_min, - tm->tm_sec); - if (tevent_req_nomem(state->fname, req)) { + SIVAL(p, 0, state->op->global->durable_timeout_msec); + SIVAL(p, 4, durable_v2_response_flags); + + status = smb2_create_blob_add(state->out_context_blobs, + state->out_context_blobs, + SMB2_CREATE_TAG_DH2Q, + blob); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); return tevent_req_post(req, state->ev); } - TALLOC_FREE(tmpname); - /* - * Tell filename_create_ucf_flags() this - * is an @GMT path. - */ - smb1req->flags2 |= FLAGS2_REPARSE_PATH; } if (state->qfid != NULL) { - if (state->qfid->data.length != 0) { - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + uint8_t p[32]; + uint64_t file_index = get_FileIndex(state->result->conn, + &state->result->fsp_name->st); + DATA_BLOB blob = data_blob_const(p, sizeof(p)); + + ZERO_STRUCT(p); + + /* From conversations with Microsoft engineers at + the MS plugfest. The first 8 bytes are the "volume index" + == inode, the second 8 bytes are the "volume id", + == dev. This will be updated in the SMB2 doc. */ + SBVAL(p, 0, file_index); + SIVAL(p, 8, state->result->fsp_name->st.st_ex_dev);/* FileIndexHigh */ + + status = smb2_create_blob_add(state->out_context_blobs, + state->out_context_blobs, + SMB2_CREATE_TAG_QFID, + blob); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); return tevent_req_post(req, state->ev); } } - if (state->rqls != NULL) { - ssize_t lease_len = -1; + if ((state->rqls != NULL) && (state->result->oplock_type == LEASE_OPLOCK)) { + uint8_t buf[52]; + struct smb2_lease lease; + size_t lease_len; - lease_len = smb2_lease_pull(state->rqls->data.data, - state->rqls->data.length, - &state->lease); - if (lease_len == -1) { + lease = state->result->lease->lease; + + lease_len = sizeof(buf); + if (lease.lease_version == 1) { + lease_len = 32; + } + + if (!smb2_lease_push(&lease, buf, lease_len)) { tevent_req_nterror( - req, NT_STATUS_INVALID_PARAMETER); + req, NT_STATUS_INTERNAL_ERROR); return tevent_req_post(req, state->ev); } - state->lease_ptr = &state->lease; - if (DEBUGLEVEL >= 10) { - DEBUG(10, ("Got lease request size %d\n", - (int)lease_len)); - NDR_PRINT_DEBUG(smb2_lease, state->lease_ptr); + status = smb2_create_blob_add( + state, state->out_context_blobs, + SMB2_CREATE_TAG_RQLS, + data_blob_const(buf, lease_len)); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, state->ev); } + } - if (!smb2_lease_key_valid(&state->lease.lease_key)) { - state->lease_ptr = NULL; - state->requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE; + smbd_smb2_create_finish(req); + return req; +} + +static void smbd_smb2_create_before_exec(struct tevent_req *req) +{ + struct smbd_smb2_create_state *state = tevent_req_data( + req, struct smbd_smb2_create_state); + struct smb_request *smb1req = state->smb1req; + struct smbd_smb2_request *smb2req = state->smb2req; + NTSTATUS status; + + if (state->exta != NULL) { + if (!lp_ea_support(SNUM(smb2req->tcon->compat))) { + tevent_req_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED); + return; } - if ((smb2req->xconn->protocol < PROTOCOL_SMB3_00) && - (state->lease.lease_version != 1)) - { - DEBUG(10, ("v2 lease key only for SMB3\n")); - state->lease_ptr = NULL; + state->ea_list = read_nttrans_ea_list( + state, + (const char *)state->exta->data.data, + state->exta->data.length); + if (state->ea_list == NULL) { + DEBUG(10,("smbd_smb2_create_send: read_ea_name_list failed.\n")); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; } /* - * Replay with a lease is only allowed if the - * established open carries a lease with the - * same lease key. + * NB. When SMB2+ unix extensions are added, + * we need to relax this check in invalid + * names - we used to not do this if + * lp_posix_pathnames() was false. */ - if (state->replay_operation) { - struct smb2_lease *op_ls = - &state->op->compat->lease->lease; - int op_oplock = state->op->compat->oplock_type; - - if (map_samba_oplock_levels_to_smb2(op_oplock) - != SMB2_OPLOCK_LEVEL_LEASE) - { - status = NT_STATUS_ACCESS_DENIED; - (void)tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); - } - if (!smb2_lease_key_equal(&state->lease.lease_key, - &op_ls->lease_key)) - { - status = NT_STATUS_ACCESS_DENIED; - (void)tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); - } + if (ea_list_has_invalid_name(state->ea_list)) { + tevent_req_nterror(req, STATUS_INVALID_EA_NAME); + return; } } - DEBUG(10, ("smbd_smb2_create_send: open execution phase\n")); - - /* - * For the backend file open procedure, there are - * three possible modes: replay operation (in which case - * there is nothing else to do), durable_reconnect or - * new open. - */ - if (state->replay_operation) { - state->result = state->op->compat; - state->result->op = state->op; - state->update_open = false; - state->info = state->op->create_action; - } else if (state->do_durable_reconnect) { - DATA_BLOB new_cookie = data_blob_null; - NTTIME now = timeval_to_nttime(&smb2req->request_time); - - status = smb2srv_open_recreate(smb2req->xconn, - smb1req->conn->session_info, - state->persistent_id, - state->create_guid, - now, - &state->op); - if (!NT_STATUS_IS_OK(status)) { - DEBUG(3, ("smbd_smb2_create_send: " - "smb2srv_open_recreate failed: %s\n", - nt_errstr(status))); - tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); + if (state->mxac != NULL) { + if (state->mxac->data.length == 0) { + state->max_access_time = 0; + } else if (state->mxac->data.length == 8) { + state->max_access_time = BVAL(state->mxac->data.data, 0); + } else { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; } + } - DEBUG(10, ("smb2_create_send: %s to recreate the " - "smb2srv_open struct for a durable handle.\n", - state->op->global->durable ? "succeeded" : "failed")); + if (state->secd != NULL) { + enum ndr_err_code ndr_err; - if (!state->op->global->durable) { - talloc_free(state->op); - tevent_req_nterror(req, - NT_STATUS_OBJECT_NAME_NOT_FOUND); - return tevent_req_post(req, state->ev); + state->sec_desc = talloc_zero(state, struct security_descriptor); + if (tevent_req_nomem(state->sec_desc, req)) { + return; } - status = SMB_VFS_DURABLE_RECONNECT(smb1req->conn, - smb1req, - state->op, /* smbXsrv_open input */ - state->op->global->backend_cookie, - state->op, /* TALLOC_CTX */ - &state->result, - &new_cookie); - if (!NT_STATUS_IS_OK(status)) { - NTSTATUS return_status; - - return_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; - - DEBUG(3, ("smbd_smb2_create_send: " - "durable_reconnect failed: %s => %s\n", - nt_errstr(status), - nt_errstr(return_status))); - - tevent_req_nterror(req, return_status); - return tevent_req_post(req, state->ev); + ndr_err = ndr_pull_struct_blob(&state->secd->data, + state->sec_desc, state->sec_desc, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(2,("ndr_pull_security_descriptor failed: %s\n", + ndr_errstr(ndr_err))); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; } + } - DEBUG(10, ("result->oplock_type=%u, lease_ptr==%p\n", - (unsigned)state->result->oplock_type, state->lease_ptr)); - - status = smbd_smb2_create_durable_lease_check( - smb1req, state->fname, state->result, state->lease_ptr); - if (!NT_STATUS_IS_OK(status)) { - close_file(smb1req, state->result, SHUTDOWN_CLOSE); - tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); + if (state->dhnq != NULL) { + if (state->dhnq->data.length != 16) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; } - data_blob_free(&state->op->global->backend_cookie); - state->op->global->backend_cookie = new_cookie; - - state->op->status = NT_STATUS_OK; - state->op->global->disconnect_time = 0; - - /* save the timout for later update */ - state->durable_timeout_msec = state->op->global->durable_timeout_msec; - - state->update_open = true; - - state->info = FILE_WAS_OPENED; - } else { - struct smb_filename *smb_fname = NULL; - uint32_t ucf_flags; - - if (state->requested_oplock_level == SMB2_OPLOCK_LEVEL_LEASE) { - if (state->lease_ptr == NULL) { - state->requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE; - } - } else { - state->lease_ptr = NULL; + if (state->dh2q != NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; } /* - * For a DFS path the function parse_dfs_path() - * will do the path processing. + * durable handle request is processed below. */ - - if (!(smb1req->flags2 & FLAGS2_DFS_PATHNAMES)) { - /* convert '\\' into '/' */ - status = check_path_syntax(state->fname); - if (!NT_STATUS_IS_OK(status)) { - tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); - } - } - - ucf_flags = filename_create_ucf_flags( - smb1req, state->in_create_disposition); - status = filename_convert(req, - smb1req->conn, - state->fname, - ucf_flags, - NULL, /* ppath_contains_wcards */ - &smb_fname); - if (!NT_STATUS_IS_OK(status)) { - tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); - } - + state->durable_requested = true; /* - * MS-SMB2: 2.2.13 SMB2 CREATE Request - * ImpersonationLevel ... MUST contain one of the - * following values. The server MUST validate this - * field, but otherwise ignore it. + * Set the timeout to 16 mins. * - * NB. The source4/torture/smb2/durable_open.c test - * shows this check is only done on real opens, not - * on durable handle-reopens. + * TODO: test this against Windows 2012 + * as the default for durable v2 is 1 min. */ + state->durable_timeout_msec = (16*60*1000); + } - if (in_impersonation_level > - SMB2_IMPERSONATION_DELEGATE) { - tevent_req_nterror(req, - NT_STATUS_BAD_IMPERSONATION_LEVEL); - return tevent_req_post(req, state->ev); - } + if (state->dh2q != NULL) { + const uint8_t *p = state->dh2q->data.data; + uint32_t durable_v2_timeout = 0; + DATA_BLOB create_guid_blob; + const uint8_t *hdr; + uint32_t flags; - /* - * We know we're going to do a local open, so now - * we must be protocol strict. JRA. - * - * MS-SMB2: 3.3.5.9 - Receiving an SMB2 CREATE Request - * If the file name length is greater than zero and the - * first character is a path separator character, the - * server MUST fail the request with - * STATUS_INVALID_PARAMETER. - */ - if (in_name[0] == '\\' || in_name[0] == '/') { - tevent_req_nterror(req, - NT_STATUS_INVALID_PARAMETER); - return tevent_req_post(req, state->ev); + if (state->dh2q->data.length != 32) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; } - status = SMB_VFS_CREATE_FILE(smb1req->conn, - smb1req, - 0, /* root_dir_fid */ - smb_fname, - in_desired_access, - in_share_access, - state->in_create_disposition, - in_create_options, - in_file_attributes, - map_smb2_oplock_levels_to_samba( - state->requested_oplock_level), - state->lease_ptr, - state->allocation_size, - 0, /* private_flags */ - state->sec_desc, - state->ea_list, - &state->result, - &state->info, - &in_context_blobs, - state->out_context_blobs); - if (!NT_STATUS_IS_OK(status)) { - if (open_was_deferred(smb1req->xconn, smb1req->mid)) { - SMBPROFILE_IOBYTES_ASYNC_SET_IDLE(smb2req->profile); - return req; - } - tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); + if (state->dhnq != NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; } - state->op = state->result->op; - } - /* - * here we have op == state->result->op - */ + durable_v2_timeout = IVAL(p, 0); + create_guid_blob = data_blob_const(p + 16, 16); - DEBUG(10, ("smbd_smb2_create_send: " - "response construction phase\n")); + status = GUID_from_ndr_blob(&create_guid_blob, + &state->_create_guid); + if (tevent_req_nterror(req, status)) { + return; + } + state->create_guid = &state->_create_guid; - if (state->mxac != NULL) { - NTTIME last_write_time; + /* + * we need to store the create_guid later + */ + state->update_open = true; - last_write_time = unix_timespec_to_nt_time( - state->result->fsp_name->st.st_ex_mtime); - if (last_write_time != state->max_access_time) { - uint8_t p[8]; - uint32_t max_access_granted; - DATA_BLOB blob = data_blob_const(p, sizeof(p)); + /* + * And we need to create a cache for replaying the + * create. + */ + state->need_replay_cache = true; - status = smbd_calculate_access_mask(smb1req->conn, - state->result->fsp_name, - false, - SEC_FLAG_MAXIMUM_ALLOWED, - &max_access_granted); + /* + * durable handle v2 request processed below + */ + state->durable_requested = true; + state->durable_timeout_msec = durable_v2_timeout; + if (state->durable_timeout_msec == 0) { + /* + * Set the timeout to 1 min as default. + * + * This matches Windows 2012. + */ + state->durable_timeout_msec = (60*1000); + } - SIVAL(p, 0, NT_STATUS_V(status)); - SIVAL(p, 4, max_access_granted); + /* + * Check for replay operation. + * Only consider it when we have dh2q. + * If we do not have a replay operation, verify that + * the create_guid is not cached for replay. + */ + hdr = SMBD_SMB2_IN_HDR_PTR(smb2req); + flags = IVAL(hdr, SMB2_HDR_FLAGS); + state->replay_operation = + flags & SMB2_HDR_FLAG_REPLAY_OPERATION; - status = smb2_create_blob_add( - state->out_context_blobs, - state->out_context_blobs, - SMB2_CREATE_TAG_MXAC, - blob); - if (!NT_STATUS_IS_OK(status)) { - tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); - } + status = smb2srv_open_lookup_replay_cache(smb2req->xconn, + state->create_guid, + 0 /* now */, + &state->op); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + state->replay_operation = false; + } else if (tevent_req_nterror(req, status)) { + DBG_WARNING("smb2srv_open_lookup_replay_cache " + "failed: %s\n", nt_errstr(status)); + return; + } else if (!state->replay_operation) { + /* + * If a create without replay operation flag + * is sent but with a create_guid that is + * currently in the replay cache -- fail. + */ + status = NT_STATUS_DUPLICATE_OBJECTID; + (void)tevent_req_nterror(req, status); + return; } } - if (!state->replay_operation && state->durable_requested && - (fsp_lease_type(state->result) & SMB2_LEASE_HANDLE)) - { - status = SMB_VFS_DURABLE_COOKIE( - state->result, - state->op, - &state->op->global->backend_cookie); - if (!NT_STATUS_IS_OK(status)) { - state->op->global->backend_cookie = data_blob_null; - } + if (state->dhnc != NULL) { + state->persistent_id = BVAL(state->dhnc->data.data, 0); + state->do_durable_reconnect = true; } - if (!state->replay_operation && state->op->global->backend_cookie.length > 0) - { - state->update_open = true; - state->op->global->durable = true; - state->op->global->durable_timeout_msec = state->durable_timeout_msec; - } + if (state->dh2c != NULL) { + const uint8_t *p = state->dh2c->data.data; + DATA_BLOB create_guid_blob; - if (state->update_open) { - state->op->global->create_guid = state->_create_guid; - if (state->need_replay_cache) { - state->op->flags |= SMBXSRV_OPEN_NEED_REPLAY_CACHE; - } + state->persistent_id = BVAL(p, 0); + create_guid_blob = data_blob_const(p + 16, 16); - status = smbXsrv_open_update(state->op); - DEBUG(10, ("smb2_create_send: smbXsrv_open_update " - "returned %s\n", - nt_errstr(status))); - if (!NT_STATUS_IS_OK(status)) { - tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); + status = GUID_from_ndr_blob(&create_guid_blob, + &state->_create_guid); + if (tevent_req_nterror(req, status)) { + return; } - } - if (state->dhnq != NULL && state->op->global->durable) { - uint8_t p[8] = { 0, }; - DATA_BLOB blob = data_blob_const(p, sizeof(p)); + state->create_guid = &state->_create_guid; + state->do_durable_reconnect = true; + } - status = smb2_create_blob_add(state->out_context_blobs, - state->out_context_blobs, - SMB2_CREATE_TAG_DHNQ, - blob); - if (!NT_STATUS_IS_OK(status)) { - tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); + if (state->alsi != NULL) { + if (state->alsi->data.length != 8) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; } + state->allocation_size = BVAL(state->alsi->data.data, 0); } - if (state->dh2q != NULL && state->op->global->durable && - /* - * For replay operations, we return the dh2q blob - * in the case of oplocks not based on the state of - * the open, but on whether it could have been granted - * for the request data. In the case of leases instead, - * the state of the open is used... - */ - (!state->replay_operation || - state->in_oplock_level == SMB2_OPLOCK_LEVEL_BATCH || - state->in_oplock_level == SMB2_OPLOCK_LEVEL_LEASE)) - { - uint8_t p[8] = { 0, }; - DATA_BLOB blob = data_blob_const(p, sizeof(p)); - uint32_t durable_v2_response_flags = 0; + if (state->twrp != NULL) { + NTTIME nttime; + time_t t; + struct tm *tm; + char *tmpname = state->fname; - SIVAL(p, 0, state->op->global->durable_timeout_msec); - SIVAL(p, 4, durable_v2_response_flags); + if (state->twrp->data.length != 8) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; + } - status = smb2_create_blob_add(state->out_context_blobs, - state->out_context_blobs, - SMB2_CREATE_TAG_DH2Q, - blob); - if (!NT_STATUS_IS_OK(status)) { - tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); + nttime = BVAL(state->twrp->data.data, 0); + t = nt_time_to_unix(nttime); + tm = gmtime(&t); + + state->fname = talloc_asprintf( + state, + "%s\\@GMT-%04u.%02u.%02u-%02u.%02u.%02u", + state->fname, + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + if (tevent_req_nomem(state->fname, req)) { + return; } + TALLOC_FREE(tmpname); + /* + * Tell filename_create_ucf_flags() this + * is an @GMT path. + */ + smb1req->flags2 |= FLAGS2_REPARSE_PATH; } if (state->qfid != NULL) { - uint8_t p[32]; - uint64_t file_index = get_FileIndex(state->result->conn, - &state->result->fsp_name->st); - DATA_BLOB blob = data_blob_const(p, sizeof(p)); - - ZERO_STRUCT(p); - - /* From conversations with Microsoft engineers at - the MS plugfest. The first 8 bytes are the "volume index" - == inode, the second 8 bytes are the "volume id", - == dev. This will be updated in the SMB2 doc. */ - SBVAL(p, 0, file_index); - SIVAL(p, 8, state->result->fsp_name->st.st_ex_dev);/* FileIndexHigh */ - - status = smb2_create_blob_add(state->out_context_blobs, - state->out_context_blobs, - SMB2_CREATE_TAG_QFID, - blob); - if (!NT_STATUS_IS_OK(status)) { - tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); + if (state->qfid->data.length != 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; } } - if ((state->rqls != NULL) && (state->result->oplock_type == LEASE_OPLOCK)) { - uint8_t buf[52]; - struct smb2_lease lease; - size_t lease_len; + if (state->rqls != NULL) { + ssize_t lease_len = -1; - lease = state->result->lease->lease; + lease_len = smb2_lease_pull(state->rqls->data.data, + state->rqls->data.length, + &state->lease); + if (lease_len == -1) { + tevent_req_nterror( + req, NT_STATUS_INVALID_PARAMETER); + return; + } + state->lease_ptr = &state->lease; - lease_len = sizeof(buf); - if (lease.lease_version == 1) { - lease_len = 32; + if (DEBUGLEVEL >= 10) { + DEBUG(10, ("Got lease request size %d\n", + (int)lease_len)); + NDR_PRINT_DEBUG(smb2_lease, state->lease_ptr); } - if (!smb2_lease_push(&lease, buf, lease_len)) { - tevent_req_nterror( - req, NT_STATUS_INTERNAL_ERROR); - return tevent_req_post(req, state->ev); + if (!smb2_lease_key_valid(&state->lease.lease_key)) { + state->lease_ptr = NULL; + state->requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE; } - status = smb2_create_blob_add( - state, state->out_context_blobs, - SMB2_CREATE_TAG_RQLS, - data_blob_const(buf, lease_len)); - if (!NT_STATUS_IS_OK(status)) { - tevent_req_nterror(req, status); - return tevent_req_post(req, state->ev); + if ((smb2req->xconn->protocol < PROTOCOL_SMB3_00) && + (state->lease.lease_version != 1)) + { + DEBUG(10, ("v2 lease key only for SMB3\n")); + state->lease_ptr = NULL; + } + + /* + * Replay with a lease is only allowed if the + * established open carries a lease with the + * same lease key. + */ + if (state->replay_operation) { + struct smb2_lease *op_ls = + &state->op->compat->lease->lease; + int op_oplock = state->op->compat->oplock_type; + + if (map_samba_oplock_levels_to_smb2(op_oplock) + != SMB2_OPLOCK_LEVEL_LEASE) + { + status = NT_STATUS_ACCESS_DENIED; + (void)tevent_req_nterror(req, status); + return; + } + if (!smb2_lease_key_equal(&state->lease.lease_key, + &op_ls->lease_key)) + { + status = NT_STATUS_ACCESS_DENIED; + (void)tevent_req_nterror(req, status); + return; + } } } - smbd_smb2_create_finish(req); - return req; + return; } static void smbd_smb2_create_finish(struct tevent_req *req)