Core SMB2 server
Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
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
*/
#include "includes.h"
+#include "printing.h"
+#include "smbd/smbd.h"
#include "smbd/globals.h"
#include "../libcli/smb/smb_common.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../lib/util/tevent_ntstatus.h"
+
+int map_smb2_oplock_levels_to_samba(uint8_t in_oplock_level)
+{
+ switch(in_oplock_level) {
+ case SMB2_OPLOCK_LEVEL_NONE:
+ return NO_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_II:
+ return LEVEL_II_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
+ return EXCLUSIVE_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_BATCH:
+ return BATCH_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_LEASE:
+ DEBUG(2,("map_smb2_oplock_levels_to_samba: "
+ "LEASE_OPLOCK_REQUESTED\n"));
+ return NO_OPLOCK;
+ default:
+ DEBUG(2,("map_smb2_oplock_levels_to_samba: "
+ "unknown level %u\n",
+ (unsigned int)in_oplock_level));
+ return NO_OPLOCK;
+ }
+}
+
+static uint8_t map_samba_oplock_levels_to_smb2(int oplock_type)
+{
+ if (BATCH_OPLOCK_TYPE(oplock_type)) {
+ return SMB2_OPLOCK_LEVEL_BATCH;
+ } else if (EXCLUSIVE_OPLOCK_TYPE(oplock_type)) {
+ return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ } else if (oplock_type == LEVEL_II_OPLOCK) {
+ /*
+ * Don't use LEVEL_II_OPLOCK_TYPE here as
+ * this also includes FAKE_LEVEL_II_OPLOCKs
+ * which are internal only.
+ */
+ return SMB2_OPLOCK_LEVEL_II;
+ } else {
+ return SMB2_OPLOCK_LEVEL_NONE;
+ }
+}
static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
uint64_t *out_allocation_size,
uint64_t *out_end_of_file,
uint32_t *out_file_attributes,
+ uint64_t *out_file_id_persistent,
uint64_t *out_file_id_volatile,
struct smb2_create_blobs *out_context_blobs);
{
const uint8_t *inbody;
int i = smb2req->current_idx;
- size_t expected_body_size = 0x39;
- size_t body_size;
uint8_t in_oplock_level;
uint32_t in_impersonation_level;
uint32_t in_desired_access;
bool ok;
struct tevent_req *tsubreq;
- if (smb2req->in.vector[i+1].iov_len != (expected_body_size & 0xFFFFFFFE)) {
- return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ status = smbd_smb2_request_verify_sizes(smb2req, 0x39);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(smb2req, status);
}
-
inbody = (const uint8_t *)smb2req->in.vector[i+1].iov_base;
- body_size = SVAL(inbody, 0x00);
- if (body_size != expected_body_size) {
- return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
- }
-
in_oplock_level = CVAL(inbody, 0x03);
in_impersonation_level = IVAL(inbody, 0x04);
in_desired_access = IVAL(inbody, 0x18);
* overlap
*/
- dyn_offset = SMB2_HDR_BODY + (body_size & 0xFFFFFFFE);
+ dyn_offset = SMB2_HDR_BODY + smb2req->in.vector[i+1].iov_len;
if (in_name_offset == 0 && in_name_length == 0) {
/* This is ok */
in_name_buffer.data,
in_name_buffer.length,
&in_name_string,
- &in_name_string_size, false);
+ &in_name_string_size);
if (!ok) {
return smbd_smb2_request_error(smb2req, NT_STATUS_ILLEGAL_CHARACTER);
}
+ if (in_name_buffer.length == 0) {
+ in_name_string_size = 0;
+ }
+
+ if (strlen(in_name_string) != in_name_string_size) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_OBJECT_NAME_INVALID);
+ }
+
ZERO_STRUCT(in_context_blobs);
status = smb2_create_blob_parse(smb2req, in_context_buffer, &in_context_blobs);
if (!NT_STATUS_IS_OK(status)) {
}
tevent_req_set_callback(tsubreq, smbd_smb2_request_create_done, smb2req);
- return smbd_smb2_request_pending_queue(smb2req, tsubreq);
+ return smbd_smb2_request_pending_queue(smb2req, tsubreq, 500);
}
static uint64_t get_mid_from_smb2req(struct smbd_smb2_request *smb2req)
uint64_t out_allocation_size = 0;
uint64_t out_end_of_file = 0;
uint32_t out_file_attributes = 0;
+ uint64_t out_file_id_persistent = 0;
uint64_t out_file_id_volatile = 0;
struct smb2_create_blobs out_context_blobs;
DATA_BLOB out_context_buffer;
&out_allocation_size,
&out_end_of_file,
&out_file_attributes,
+ &out_file_id_persistent,
&out_file_id_volatile,
&out_context_blobs);
if (!NT_STATUS_IS_OK(status)) {
SIVAL(outbody.data, 0x38,
out_file_attributes); /* file attributes */
SIVAL(outbody.data, 0x3C, 0); /* reserved */
- SBVAL(outbody.data, 0x40, 0); /* file id (persistent) */
+ SBVAL(outbody.data, 0x40,
+ out_file_id_persistent); /* file id (persistent) */
SBVAL(outbody.data, 0x48,
out_file_id_volatile); /* file id (volatile) */
SIVAL(outbody.data, 0x50,
struct smbd_smb2_request *smb2req;
struct smb_request *smb1req;
struct timed_event *te;
+ struct tevent_immediate *im;
struct timeval request_time;
struct file_id id;
DATA_BLOB private_data;
uint64_t out_allocation_size;
uint64_t out_end_of_file;
uint32_t out_file_attributes;
+ uint64_t out_file_id_persistent;
uint64_t out_file_id_volatile;
struct smb2_create_blobs out_context_blobs;
};
int info;
struct timespec write_time_ts;
struct smb2_create_blobs out_context_blobs;
+ int requested_oplock_level;
ZERO_STRUCT(out_context_blobs);
- if (!smb2req->async) {
+ if(lp_fake_oplocks(SNUM(smb2req->tcon->compat_conn))) {
+ requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ } else {
+ requested_oplock_level = in_oplock_level;
+ }
+
+
+ if (smb2req->subreq == NULL) {
/* New create call. */
req = tevent_req_create(mem_ctx, &state,
struct smbd_smb2_create_state);
return NULL;
}
state->smb2req = smb2req;
- smb2req->subreq = req; /* So we can find this when going async. */
smb1req = smbd_smb2_fake_smb_request(smb2req);
if (tevent_req_nomem(smb1req, req)) {
return tevent_req_post(req, ev);
}
- status = print_fsp_open(smb1req,
- smb1req->conn,
- in_name,
- smb1req->vuid,
- result);
+ status = print_spool_open(result, in_name,
+ smb1req->vuid);
if (!NT_STATUS_IS_OK(status)) {
file_free(smb1req, result);
tevent_req_nterror(req, status);
if (exta) {
if (dhnc) {
- tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ tevent_req_nterror(req,NT_STATUS_OBJECT_NAME_NOT_FOUND);
return tevent_req_post(req, ev);
}
if (mxac) {
if (dhnc) {
- tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
return tevent_req_post(req, ev);
}
enum ndr_err_code ndr_err;
if (dhnc) {
- tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
return tevent_req_post(req, ev);
}
}
ndr_err = ndr_pull_struct_blob(&secd->data,
- sec_desc, NULL, sec_desc,
+ sec_desc, 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",
if (dhnq) {
if (dhnc) {
- tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
return tevent_req_post(req, ev);
}
if (alsi) {
if (dhnc) {
- tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
return tevent_req_post(req, ev);
}
struct tm *tm;
if (dhnc) {
- tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
return tevent_req_post(req, ev);
}
in_create_options &= ~(0x10);/* NTCREATEX_OPTIONS_SYNC_ALERT */
in_create_options &= ~(0x20);/* NTCREATEX_OPTIONS_ASYNC_ALERT */
- /* convert '\\' into '/' */
- status = check_path_syntax(fname);
- if (!NT_STATUS_IS_OK(status)) {
- tevent_req_nterror(req, status);
- return tevent_req_post(req, ev);
+ /*
+ * For a DFS path the function parse_dfs_path()
+ * will do the path processing.
+ */
+
+ if (!(smb1req->flags2 & FLAGS2_DFS_PATHNAMES)) {
+ /* convert '\\' into '/' */
+ status = check_path_syntax(fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
}
status = filename_convert(req,
return tevent_req_post(req, ev);
}
+ in_file_attributes &= ~FILE_FLAG_POSIX_SEMANTICS;
+
status = SMB_VFS_CREATE_FILE(smb1req->conn,
smb1req,
0, /* root_dir_fid */
in_create_disposition,
in_create_options,
in_file_attributes,
- 0, /* oplock_request */
+ map_smb2_oplock_levels_to_samba(requested_oplock_level),
allocation_size,
0, /* private_flags */
sec_desc,
&result,
&info);
if (!NT_STATUS_IS_OK(status)) {
- if (open_was_deferred(smb1req->mid)) {
+ if (open_was_deferred(smb1req->sconn, smb1req->mid)) {
return req;
}
tevent_req_nterror(req, status);
uint32_t max_access_granted;
DATA_BLOB blob = data_blob_const(p, sizeof(p));
- status = smbd_check_open_rights(smb1req->conn,
+ status = smbd_calculate_access_mask(smb1req->conn,
result->fsp_name,
+ /*
+ * at this stage
+ * it exists
+ */
+ true,
SEC_FLAG_MAXIMUM_ALLOWED,
&max_access_granted);
if (qfid) {
uint8_t p[32];
+ uint64_t file_index = get_FileIndex(result->conn,
+ &result->fsp_name->st);
DATA_BLOB blob = data_blob_const(p, sizeof(p));
ZERO_STRUCT(p);
- /* TODO: maybe use result->file_id */
- SIVAL(p, 0, result->fsp_name->st.st_ex_ino);/* FileIndexLow */
- SIVAL(p, 4, result->fsp_name->st.st_ex_dev);/* FileIndexHigh */
+ /* 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, result->fsp_name->st.st_ex_dev);/* FileIndexHigh */
status = smb2_create_blob_add(state, &out_context_blobs,
SMB2_CREATE_TAG_QFID,
smb2req->compat_chain_fsp = smb1req->chain_fsp;
- state->out_oplock_level = 0;
+ if(lp_fake_oplocks(SNUM(smb2req->tcon->compat_conn))) {
+ state->out_oplock_level = in_oplock_level;
+ } else {
+ state->out_oplock_level = map_samba_oplock_levels_to_smb2(result->oplock_type);
+ }
+
if ((in_create_disposition == FILE_SUPERSEDE)
&& (info == FILE_WAS_OVERWRITTEN)) {
state->out_create_action = FILE_WAS_SUPERSEDED;
/* Deal with other possible opens having a modified
write time. JRA. */
ZERO_STRUCT(write_time_ts);
- get_file_infos(result->file_id, NULL, &write_time_ts);
+ get_file_infos(result->file_id, 0, NULL, &write_time_ts);
if (!null_timespec(write_time_ts)) {
update_stat_ex_mtime(&result->fsp_name->st, write_time_ts);
}
get_change_timespec(smb1req->conn, result,
result->fsp_name));
state->out_allocation_size =
- result->fsp_name->st.st_ex_blksize *
- result->fsp_name->st.st_ex_blocks;
+ SMB_VFS_GET_ALLOC_SIZE(smb1req->conn, result,
+ &(result->fsp_name->st));
state->out_end_of_file = result->fsp_name->st.st_ex_size;
if (state->out_file_attributes == 0) {
state->out_file_attributes = FILE_ATTRIBUTE_NORMAL;
}
+ state->out_file_id_persistent = result->fnum;
state->out_file_id_volatile = result->fnum;
state->out_context_blobs = out_context_blobs;
uint64_t *out_allocation_size,
uint64_t *out_end_of_file,
uint32_t *out_file_attributes,
+ uint64_t *out_file_id_persistent,
uint64_t *out_file_id_volatile,
struct smb2_create_blobs *out_context_blobs)
{
*out_allocation_size = state->out_allocation_size;
*out_end_of_file = state->out_end_of_file;
*out_file_attributes = state->out_file_attributes;
+ *out_file_id_persistent = state->out_file_id_persistent;
*out_file_id_volatile = state->out_file_id_volatile;
*out_context_blobs = state->out_context_blobs;
if (!smb2req) {
return false;
}
- if (!smb2req->async) {
+ if (smb2req->subreq == NULL) {
return false;
}
req = smb2req->subreq;
close.
*********************************************************/
-static struct smbd_smb2_request *find_open_smb2req(uint64_t mid)
+static struct smbd_smb2_request *find_open_smb2req(
+ struct smbd_server_connection *sconn, uint64_t mid)
{
- struct smbd_server_connection *sconn = smbd_server_conn;
struct smbd_smb2_request *smb2req;
for (smb2req = sconn->smb2.requests; smb2req; smb2req = smb2req->next) {
- uint64_t message_id = get_mid_from_smb2req(smb2req);
+ uint64_t message_id;
+ if (smb2req->subreq == NULL) {
+ /* This message has been processed. */
+ continue;
+ }
+ if (!tevent_req_is_in_progress(smb2req->subreq)) {
+ /* This message has been processed. */
+ continue;
+ }
+ message_id = get_mid_from_smb2req(smb2req);
if (message_id == mid) {
return smb2req;
}
return NULL;
}
-bool open_was_deferred_smb2(uint64_t mid)
+bool open_was_deferred_smb2(struct smbd_server_connection *sconn, uint64_t mid)
{
struct smbd_smb2_create_state *state = NULL;
- struct smbd_smb2_request *smb2req = find_open_smb2req(mid);
+ struct smbd_smb2_request *smb2req;
+
+ smb2req = find_open_smb2req(sconn, mid);
if (!smb2req) {
DEBUG(10,("open_was_deferred_smb2: mid %llu smb2req == NULL\n",
/* Ensure we don't have any outstanding timer event. */
TALLOC_FREE(state->te);
+ /* Ensure we don't have any outstanding immediate event. */
+ TALLOC_FREE(state->im);
}
-void remove_deferred_open_message_smb2(uint64_t mid)
+void remove_deferred_open_message_smb2(
+ struct smbd_server_connection *sconn, uint64_t mid)
{
- struct smbd_smb2_request *smb2req = find_open_smb2req(mid);
+ struct smbd_smb2_request *smb2req;
+
+ smb2req = find_open_smb2req(sconn, mid);
if (!smb2req) {
DEBUG(10,("remove_deferred_open_message_smb2: "
remove_deferred_open_message_smb2_internal(smb2req, mid);
}
-void schedule_deferred_open_message_smb2(uint64_t mid)
+static void smbd_smb2_create_request_dispatch_immediate(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct smbd_smb2_request *smb2req = talloc_get_type_abort(private_data,
+ struct smbd_smb2_request);
+ struct smbd_server_connection *sconn = smb2req->sconn;
+ uint64_t mid = get_mid_from_smb2req(smb2req);
+ NTSTATUS status;
+
+ DEBUG(10,("smbd_smb2_create_request_dispatch_immediate: "
+ "re-dispatching mid %llu\n",
+ (unsigned long long)mid ));
+
+ status = smbd_smb2_request_dispatch(smb2req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(sconn, nt_errstr(status));
+ return;
+ }
+}
+
+void schedule_deferred_open_message_smb2(
+ struct smbd_server_connection *sconn, uint64_t mid)
{
- struct tevent_immediate *im = NULL;
struct smbd_smb2_create_state *state = NULL;
- struct smbd_smb2_request *smb2req = find_open_smb2req(mid);
+ struct smbd_smb2_request *smb2req;
+
+ smb2req = find_open_smb2req(sconn, mid);
if (!smb2req) {
DEBUG(10,("schedule_deferred_open_message_smb2: "
if (!state) {
return;
}
+
/* Ensure we don't have any outstanding timer event. */
TALLOC_FREE(state->te);
+ /* Ensure we don't have any outstanding immediate event. */
+ TALLOC_FREE(state->im);
- im = tevent_create_immediate(smb2req);
- if (!im) {
+ /*
+ * This is subtle. We must null out the callback
+ * before resheduling, else the first call to
+ * tevent_req_nterror() causes the _receive()
+ * function to be called, this causing tevent_req_post()
+ * to crash.
+ */
+ tevent_req_set_callback(smb2req->subreq, NULL, NULL);
+
+ state->im = tevent_create_immediate(smb2req);
+ if (!state->im) {
smbd_server_connection_terminate(smb2req->sconn,
nt_errstr(NT_STATUS_NO_MEMORY));
+ return;
}
DEBUG(10,("schedule_deferred_open_message_smb2: "
"re-processing mid %llu\n",
(unsigned long long)mid ));
- tevent_schedule_immediate(im,
+ tevent_schedule_immediate(state->im,
smb2req->sconn->smb2.event_ctx,
- smbd_smb2_request_dispatch_immediate,
+ smbd_smb2_create_request_dispatch_immediate,
smb2req);
}
* this returns.
*/
state->te = NULL;
+ /* Ensure we don't have any outstanding immediate event. */
+ TALLOC_FREE(state->im);
/*
* This is subtle. We must null out the callback
smb2req = state->smb2req;
mid = get_mid_from_smb2req(smb2req);
- remove_deferred_open_entry(state->id, mid);
+ remove_deferred_open_entry(state->id, mid,
+ sconn_server_id(smb2req->sconn));
remove_deferred_open_message_smb2_internal(smb2req, mid);
smb2req->cancelled = true;
if (!state->private_data.data) {
return false;
}
-#if 0
- /* Boo - turns out this isn't what W2K8R2
- does. It actually sends the STATUS_PENDING
- message followed by the STATUS_SHARING_VIOLATION
- message. Surely this means that all open
- calls (even on directories) will potentially
- fail in a chain.... ? And I've seen directory
- opens as the start of a chain. JRA.
- */
- /*
- * More subtlety. To match W2K8R2 don't
- * send a "gone async" message if it's simply
- * a STATUS_SHARING_VIOLATION (short) wait, not
- * an oplock break wait. We do this by prematurely
- * setting smb2req->async flag.
- */
- if (timeout.tv_sec < 2) {
- DEBUG(10,("push_deferred_open_message_smb2: "
- "short timer wait (usec = %u). "
- "Don't send async message.\n",
- (unsigned int)timeout.tv_usec ));
- smb2req->async = true;
- }
-#endif
/* Re-schedule us to retry on timer expiry. */
end_time = timeval_sum(&request_time, &timeout);