Copyright (C) Andrew Tridgell 1992-1998
Copyright (C) Jeremy Allison 2001-2004
Copyright (C) Volker Lendecke 2005
+ Copyright (C) Ralph Boehme 2017
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
bool delayed_for_oplocks;
bool async_open;
struct file_id id;
+
+ /*
+ * Timer for async opens, needed because they don't use a watch on
+ * a locking.tdb record. This is currently only used for real async
+ * opens and just terminates smbd if the async open times out.
+ */
+ struct tevent_timer *te;
};
/****************************************************************************
struct security_descriptor *parent_sd = NULL;
uint32_t access_granted = 0;
struct smb_filename *parent_smb_fname = NULL;
+ struct share_mode_lock *lck = NULL;
+ struct file_id id = {0};
+ uint32_t name_hash;
+ bool delete_on_close_set;
+ int ret;
if (!parent_dirname(talloc_tos(),
smb_fname->base_name,
return status;
}
- return NT_STATUS_OK;
+ if (!(access_mask & (SEC_DIR_ADD_FILE | SEC_DIR_ADD_SUBDIR))) {
+ return NT_STATUS_OK;
+ }
+ if (!lp_check_parent_directory_delete_on_close(SNUM(conn))) {
+ return NT_STATUS_OK;
+ }
+
+ /* Check if the directory has delete-on-close set */
+ ret = SMB_VFS_STAT(conn, parent_smb_fname);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+
+ id = SMB_VFS_FILE_ID_CREATE(conn, &parent_smb_fname->st);
+
+ status = file_name_hash(conn, parent_smb_fname->base_name, &name_hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ lck = get_existing_share_mode_lock(talloc_tos(), id);
+ if (lck == NULL) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ delete_on_close_set = is_delete_on_close_set(lck, name_hash);
+ if (delete_on_close_set) {
+ status = NT_STATUS_DELETE_PENDING;
+ goto out;
+ }
+
+ status = NT_STATUS_OK;
+
+out:
+ TALLOC_FREE(lck);
+ TALLOC_FREE(parent_smb_fname);
+ return status;
}
/****************************************************************************
access_mask);
}
+/****************************************************************************
+ Handle differing symlink errno's
+****************************************************************************/
+
+static int link_errno_convert(int err)
+{
+#if defined(ENOTSUP) && defined(OSF1)
+ /* handle special Tru64 errno */
+ if (err == ENOTSUP) {
+ err = ELOOP;
+ }
+#endif /* ENOTSUP */
+#ifdef EFTYPE
+ /* fix broken NetBSD errno */
+ if (err == EFTYPE) {
+ err = ELOOP;
+ }
+#endif /* EFTYPE */
+ /* fix broken FreeBSD errno */
+ if (err == EMLINK) {
+ err = ELOOP;
+ }
+ return err;
+}
+
+static int non_widelink_open(struct connection_struct *conn,
+ const struct smb_filename *conn_rootdir_fname,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ int flags,
+ mode_t mode,
+ unsigned int link_depth);
+
+/****************************************************************************
+ Follow a symlink in userspace.
+****************************************************************************/
+
+static int process_symlink_open(struct connection_struct *conn,
+ const struct smb_filename *conn_rootdir_fname,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ int flags,
+ mode_t mode,
+ unsigned int link_depth)
+{
+ int fd = -1;
+ char *link_target = NULL;
+ struct smb_filename target_fname = {0};
+ int link_len = -1;
+ struct smb_filename *oldwd_fname = NULL;
+ size_t rootdir_len = 0;
+ struct smb_filename *resolved_fname = NULL;
+ char *resolved_name = NULL;
+ bool matched = false;
+ int saved_errno = 0;
+
+ /*
+ * Ensure we don't get stuck in a symlink loop.
+ */
+ link_depth++;
+ if (link_depth >= 20) {
+ errno = ELOOP;
+ goto out;
+ }
+
+ /* Allocate space for the link target. */
+ link_target = talloc_array(talloc_tos(), char, PATH_MAX);
+ if (link_target == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ /* Read the link target. */
+ link_len = SMB_VFS_READLINK(conn,
+ smb_fname,
+ link_target,
+ PATH_MAX - 1);
+ if (link_len == -1) {
+ goto out;
+ }
+
+ /* Ensure it's at least null terminated. */
+ link_target[link_len] = '\0';
+ target_fname = (struct smb_filename){ .base_name = link_target };
+
+ /* Convert to an absolute path. */
+ resolved_fname = SMB_VFS_REALPATH(conn, talloc_tos(), &target_fname);
+ if (resolved_fname == NULL) {
+ goto out;
+ }
+ resolved_name = resolved_fname->base_name;
+
+ /*
+ * We know conn_rootdir starts with '/' and
+ * does not end in '/'. FIXME ! Should we
+ * smb_assert this ?
+ */
+ rootdir_len = strlen(conn_rootdir_fname->base_name);
+
+ matched = (strncmp(conn_rootdir_fname->base_name,
+ resolved_name,
+ rootdir_len) == 0);
+ if (!matched) {
+ errno = EACCES;
+ goto out;
+ }
+
+ /*
+ * Turn into a path relative to the share root.
+ */
+ if (resolved_name[rootdir_len] == '\0') {
+ /* Link to the root of the share. */
+ TALLOC_FREE(smb_fname->base_name);
+ smb_fname->base_name = talloc_strdup(smb_fname, ".");
+ } else if (resolved_name[rootdir_len] == '/') {
+ TALLOC_FREE(smb_fname->base_name);
+ smb_fname->base_name = talloc_strdup(smb_fname,
+ &resolved_name[rootdir_len+1]);
+ } else {
+ errno = EACCES;
+ goto out;
+ }
+
+ if (smb_fname->base_name == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ oldwd_fname = vfs_GetWd(talloc_tos(), conn);
+ if (oldwd_fname == NULL) {
+ goto out;
+ }
+
+ /* Ensure we operate from the root of the share. */
+ if (vfs_ChDir(conn, conn_rootdir_fname) == -1) {
+ goto out;
+ }
+
+ /* And do it all again.. */
+ fd = non_widelink_open(conn,
+ conn_rootdir_fname,
+ fsp,
+ smb_fname,
+ flags,
+ mode,
+ link_depth);
+ if (fd == -1) {
+ saved_errno = errno;
+ }
+
+ out:
+
+ TALLOC_FREE(resolved_fname);
+ TALLOC_FREE(link_target);
+ if (oldwd_fname != NULL) {
+ int ret = vfs_ChDir(conn, oldwd_fname);
+ if (ret == -1) {
+ smb_panic("unable to get back to old directory\n");
+ }
+ TALLOC_FREE(oldwd_fname);
+ }
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return fd;
+}
+
+/****************************************************************************
+ Non-widelink open.
+****************************************************************************/
+
+static int non_widelink_open(struct connection_struct *conn,
+ const struct smb_filename *conn_rootdir_fname,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ int flags,
+ mode_t mode,
+ unsigned int link_depth)
+{
+ NTSTATUS status;
+ int fd = -1;
+ struct smb_filename *smb_fname_rel = NULL;
+ int saved_errno = 0;
+ struct smb_filename *oldwd_fname = NULL;
+ char *parent_dir = NULL;
+ struct smb_filename parent_dir_fname = {0};
+ const char *final_component = NULL;
+ bool is_directory = false;
+ bool ok;
+
+#ifdef O_DIRECTORY
+ if (flags & O_DIRECTORY) {
+ is_directory = true;
+ }
+#endif
+
+ if (is_directory) {
+ parent_dir = talloc_strdup(talloc_tos(), smb_fname->base_name);
+ if (parent_dir == NULL) {
+ saved_errno = errno;
+ goto out;
+ }
+
+ final_component = ".";
+ } else {
+ ok = parent_dirname(talloc_tos(),
+ smb_fname->base_name,
+ &parent_dir,
+ &final_component);
+ if (!ok) {
+ saved_errno = errno;
+ goto out;
+ }
+ }
+
+ parent_dir_fname = (struct smb_filename) { .base_name = parent_dir };
+
+ oldwd_fname = vfs_GetWd(talloc_tos(), conn);
+ if (oldwd_fname == NULL) {
+ goto out;
+ }
+
+ /* Pin parent directory in place. */
+ if (vfs_ChDir(conn, &parent_dir_fname) == -1) {
+ goto out;
+ }
+
+ smb_fname_rel = synthetic_smb_fname(talloc_tos(),
+ final_component,
+ smb_fname->stream_name,
+ &smb_fname->st,
+ smb_fname->flags);
+ if (smb_fname_rel == NULL) {
+ saved_errno = ENOMEM;
+ goto out;
+ }
+
+ /* Ensure the relative path is below the share. */
+ status = check_reduced_name(conn, &parent_dir_fname, smb_fname_rel);
+ if (!NT_STATUS_IS_OK(status)) {
+ saved_errno = map_errno_from_nt_status(status);
+ goto out;
+ }
+
+ flags |= O_NOFOLLOW;
+
+ {
+ struct smb_filename *tmp_name = fsp->fsp_name;
+ fsp->fsp_name = smb_fname_rel;
+ fd = SMB_VFS_OPEN(conn, smb_fname_rel, fsp, flags, mode);
+ fsp->fsp_name = tmp_name;
+ }
+
+ if (fd == -1) {
+ saved_errno = link_errno_convert(errno);
+ /*
+ * Trying to open a symlink to a directory with O_NOFOLLOW and
+ * O_DIRECTORY can return either of ELOOP and ENOTDIR. So
+ * ENOTDIR really means: might be a symlink, but we're not sure.
+ * In this case, we just assume there's a symlink. If we were
+ * wrong, process_symlink_open() will return EINVAL. We check
+ * this below, and fall back to returning the initial
+ * saved_errno.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=12860
+ */
+ if (saved_errno == ELOOP || saved_errno == ENOTDIR) {
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) {
+ /* Never follow symlinks on posix open. */
+ goto out;
+ }
+ if (!lp_follow_symlinks(SNUM(conn))) {
+ /* Explicitly no symlinks. */
+ goto out;
+ }
+ /*
+ * We may have a symlink. Follow in userspace
+ * to ensure it's under the share definition.
+ */
+ fd = process_symlink_open(conn,
+ conn_rootdir_fname,
+ fsp,
+ smb_fname_rel,
+ flags,
+ mode,
+ link_depth);
+ if (fd == -1) {
+ if (saved_errno == ENOTDIR &&
+ errno == EINVAL) {
+ /*
+ * O_DIRECTORY on neither a directory,
+ * nor a symlink. Just return
+ * saved_errno from initial open()
+ */
+ goto out;
+ }
+ saved_errno =
+ link_errno_convert(errno);
+ }
+ }
+ }
+
+ out:
+
+ TALLOC_FREE(parent_dir);
+ TALLOC_FREE(smb_fname_rel);
+
+ if (oldwd_fname != NULL) {
+ int ret = vfs_ChDir(conn, oldwd_fname);
+ if (ret == -1) {
+ smb_panic("unable to get back to old directory\n");
+ }
+ TALLOC_FREE(oldwd_fname);
+ }
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return fd;
+}
+
/****************************************************************************
fd support routines - attempt to do a dos_open.
****************************************************************************/
struct smb_filename *smb_fname = fsp->fsp_name;
NTSTATUS status = NT_STATUS_OK;
-#ifdef O_NOFOLLOW
- /*
+ /*
* Never follow symlinks on a POSIX client. The
* client should be doing this.
*/
if ((fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) || !lp_follow_symlinks(SNUM(conn))) {
flags |= O_NOFOLLOW;
}
-#endif
- fsp->fh->fd = SMB_VFS_OPEN(conn, smb_fname, fsp, flags, mode);
- if (fsp->fh->fd == -1) {
- int posix_errno = errno;
-#ifdef O_NOFOLLOW
-#if defined(ENOTSUP) && defined(OSF1)
- /* handle special Tru64 errno */
- if (errno == ENOTSUP) {
- posix_errno = ELOOP;
+ /* Ensure path is below share definition. */
+ if (!lp_widelinks(SNUM(conn))) {
+ struct smb_filename *conn_rootdir_fname = NULL;
+ const char *conn_rootdir = SMB_VFS_CONNECTPATH(conn,
+ smb_fname);
+ int saved_errno = 0;
+
+ if (conn_rootdir == NULL) {
+ return NT_STATUS_NO_MEMORY;
}
-#endif /* ENOTSUP */
-#ifdef EFTYPE
- /* fix broken NetBSD errno */
- if (errno == EFTYPE) {
- posix_errno = ELOOP;
+ conn_rootdir_fname = synthetic_smb_fname(talloc_tos(),
+ conn_rootdir,
+ NULL,
+ NULL,
+ 0);
+ if (conn_rootdir_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
}
-#endif /* EFTYPE */
- /* fix broken FreeBSD errno */
- if (errno == EMLINK) {
- posix_errno = ELOOP;
+
+ /*
+ * Only follow symlinks within a share
+ * definition.
+ */
+ fsp->fh->fd = non_widelink_open(conn,
+ conn_rootdir_fname,
+ fsp,
+ smb_fname,
+ flags,
+ mode,
+ 0);
+ if (fsp->fh->fd == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(conn_rootdir_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
}
-#endif /* O_NOFOLLOW */
+ } else {
+ fsp->fh->fd = SMB_VFS_OPEN(conn, smb_fname, fsp, flags, mode);
+ }
+
+ if (fsp->fh->fd == -1) {
+ int posix_errno = link_errno_convert(errno);
status = map_nt_error_from_unix(posix_errno);
if (errno == EMFILE) {
static time_t last_warned = 0L;
TALLOC_FREE(smb_fname_parent);
}
-NTSTATUS change_dir_owner_to_parent(connection_struct *conn,
- const char *inherit_from_dir,
- const char *fname,
- SMB_STRUCT_STAT *psbuf)
+static NTSTATUS change_dir_owner_to_parent(connection_struct *conn,
+ const char *inherit_from_dir,
+ struct smb_filename *smb_dname,
+ SMB_STRUCT_STAT *psbuf)
{
struct smb_filename *smb_fname_parent;
struct smb_filename *smb_fname_cwd = NULL;
- char *saved_dir = NULL;
+ struct smb_filename *saved_dir_fname = NULL;
TALLOC_CTX *ctx = talloc_tos();
NTSTATUS status = NT_STATUS_OK;
int ret;
should work on any UNIX (thanks tridge :-). JRA.
*/
- saved_dir = vfs_GetWd(ctx,conn);
- if (!saved_dir) {
+ saved_dir_fname = vfs_GetWd(ctx,conn);
+ if (!saved_dir_fname) {
status = map_nt_error_from_unix(errno);
DEBUG(0,("change_dir_owner_to_parent: failed to get "
"current working directory. Error was %s\n",
}
/* Chdir into the new path. */
- if (vfs_ChDir(conn, fname) == -1) {
+ if (vfs_ChDir(conn, smb_dname) == -1) {
status = map_nt_error_from_unix(errno);
DEBUG(0,("change_dir_owner_to_parent: failed to change "
"current working directory to %s. Error "
- "was %s\n", fname, strerror(errno) ));
+ "was %s\n", smb_dname->base_name, strerror(errno) ));
goto chdir;
}
status = map_nt_error_from_unix(errno);
DEBUG(0,("change_dir_owner_to_parent: failed to stat "
"directory '.' (%s) Error was %s\n",
- fname, strerror(errno)));
+ smb_dname->base_name, strerror(errno)));
goto chdir;
}
smb_fname_cwd->st.st_ex_ino != psbuf->st_ex_ino) {
DEBUG(0,("change_dir_owner_to_parent: "
"device/inode on directory %s changed. "
- "Refusing to chown !\n", fname ));
+ "Refusing to chown !\n",
+ smb_dname->base_name ));
status = NT_STATUS_ACCESS_DENIED;
goto chdir;
}
/* Already this uid - no need to change. */
DEBUG(10,("change_dir_owner_to_parent: directory %s "
"is already owned by uid %d\n",
- fname,
+ smb_dname->base_name,
(int)smb_fname_cwd->st.st_ex_uid ));
status = NT_STATUS_OK;
goto chdir;
status = map_nt_error_from_unix(errno);
DEBUG(10,("change_dir_owner_to_parent: failed to chown "
"directory %s to parent directory uid %u. "
- "Error was %s\n", fname,
+ "Error was %s\n",
+ smb_dname->base_name,
(unsigned int)smb_fname_parent->st.st_ex_uid,
strerror(errno) ));
} else {
DEBUG(10,("change_dir_owner_to_parent: changed ownership of new "
"directory %s to parent directory uid %u.\n",
- fname, (unsigned int)smb_fname_parent->st.st_ex_uid ));
+ smb_dname->base_name,
+ (unsigned int)smb_fname_parent->st.st_ex_uid ));
/* Ensure the uid entry is updated. */
psbuf->st_ex_uid = smb_fname_parent->st.st_ex_uid;
}
chdir:
- vfs_ChDir(conn,saved_dir);
+ vfs_ChDir(conn, saved_dir_fname);
out:
+ TALLOC_FREE(saved_dir_fname);
TALLOC_FREE(smb_fname_parent);
TALLOC_FREE(smb_fname_cwd);
return status;
/* Inherit the ACL if required */
if (lp_inherit_permissions(SNUM(conn))) {
inherit_access_posix_acl(conn, parent_dir,
- smb_fname->base_name,
+ smb_fname,
unx_mode);
need_re_stat = true;
}
#if defined(DEVELOPER)
static void validate_my_share_entries(struct smbd_server_connection *sconn,
+ const struct file_id id,
int num,
struct share_mode_entry *share_entry)
{
return;
}
- fsp = file_find_dif(sconn, share_entry->id,
- share_entry->share_file_id);
+ fsp = file_find_dif(sconn, id, share_entry->share_file_id);
if (!fsp) {
- DEBUG(0,("validate_my_share_entries: PANIC : %s\n",
- share_mode_str(talloc_tos(), num, share_entry) ));
+ DBG_ERR("PANIC : %s\n",
+ share_mode_str(talloc_tos(), num, &id,
+ share_entry));
smb_panic("validate_my_share_entries: Cannot match a "
"share entry with an open file\n");
}
panic:
{
char *str;
- DEBUG(0,("validate_my_share_entries: PANIC : %s\n",
- share_mode_str(talloc_tos(), num, share_entry) ));
+ DBG_ERR("validate_my_share_entries: PANIC : %s\n",
+ share_mode_str(talloc_tos(), num, &id,
+ share_entry));
str = talloc_asprintf(talloc_tos(),
"validate_my_share_entries: "
"file %s, oplock_type = 0x%x, op_type = 0x%x\n",
#if defined(DEVELOPER)
for(i = 0; i < lck->data->num_share_modes; i++) {
- validate_my_share_entries(conn->sconn, i,
+ validate_my_share_entries(conn->sconn, lck->data->id, i,
&lck->data->share_modes[i]);
}
#endif
*/
NTSTATUS send_break_message(struct messaging_context *msg_ctx,
- const struct share_mode_entry *exclusive,
- uint16_t break_to)
+ const struct file_id *id,
+ const struct share_mode_entry *exclusive,
+ uint16_t break_to)
{
NTSTATUS status;
char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
server_id_str_buf(exclusive->pid, &tmp)));
/* Create the message. */
- share_mode_entry_to_message(msg, exclusive);
+ share_mode_entry_to_message(msg, id, exclusive);
/* Overload entry->op_type */
/*
DEBUG(10, ("breaking from %d to %d\n",
(int)e_lease_type, (int)break_to));
- send_break_message(fsp->conn->sconn->msg_ctx, e,
- break_to);
+ send_break_message(fsp->conn->sconn->msg_ctx, &fsp->file_id,
+ e, break_to);
if (e_lease_type & delay_mask) {
delay = true;
}
static void defer_open_done(struct tevent_req *req);
-/****************************************************************************
- Handle the 1 second delay in returning a SHARING_VIOLATION error.
-****************************************************************************/
-
+/**
+ * Defer an open and watch a locking.tdb record
+ *
+ * This defers an open that gets rescheduled once the locking.tdb record watch
+ * is triggered by a change to the record.
+ *
+ * It is used to defer opens that triggered an oplock break and for the SMB1
+ * sharing violation delay.
+ **/
static void defer_open(struct share_mode_lock *lck,
struct timeval request_time,
struct timeval timeout,
struct smb_request *req,
bool delayed_for_oplocks,
- bool async_open,
struct file_id id)
{
struct deferred_open_record *open_rec = NULL;
struct timeval abs_timeout;
+ struct defer_open_state *watch_state;
+ struct tevent_req *watch_req;
+ bool ok;
abs_timeout = timeval_sum(&request_time, &timeout);
DBG_DEBUG("request time [%s] timeout [%s] mid [%" PRIu64 "] "
- "delayed_for_oplocks [%s] async_open [%s] file_id [%s]\n",
+ "delayed_for_oplocks [%s] file_id [%s]\n",
timeval_string(talloc_tos(), &request_time, false),
timeval_string(talloc_tos(), &abs_timeout, false),
req->mid,
delayed_for_oplocks ? "yes" : "no",
- async_open ? "yes" : "no",
file_id_string_tos(&id));
open_rec = deferred_open_record_create(delayed_for_oplocks,
- async_open,
+ false,
id);
if (open_rec == NULL) {
TALLOC_FREE(lck);
exit_server("talloc failed");
}
- if (lck) {
- struct defer_open_state *watch_state;
- struct tevent_req *watch_req;
- bool ret;
-
- watch_state = talloc(open_rec, struct defer_open_state);
- if (watch_state == NULL) {
- exit_server("talloc failed");
- }
- watch_state->xconn = req->xconn;
- watch_state->mid = req->mid;
+ watch_state = talloc(open_rec, struct defer_open_state);
+ if (watch_state == NULL) {
+ exit_server("talloc failed");
+ }
+ watch_state->xconn = req->xconn;
+ watch_state->mid = req->mid;
- DEBUG(10, ("defering mid %llu\n",
- (unsigned long long)req->mid));
+ DBG_DEBUG("defering mid %" PRIu64 "\n", req->mid);
- watch_req = dbwrap_watched_watch_send(
- watch_state, req->sconn->ev_ctx, lck->data->record,
- (struct server_id){0});
- if (watch_req == NULL) {
- exit_server("Could not watch share mode record");
- }
- tevent_req_set_callback(watch_req, defer_open_done,
- watch_state);
+ watch_req = dbwrap_watched_watch_send(watch_state,
+ req->ev_ctx,
+ lck->data->record,
+ (struct server_id){0});
+ if (watch_req == NULL) {
+ exit_server("Could not watch share mode record");
+ }
+ tevent_req_set_callback(watch_req, defer_open_done, watch_state);
- ret = tevent_req_set_endtime(
- watch_req, req->sconn->ev_ctx,
- abs_timeout);
- SMB_ASSERT(ret);
+ ok = tevent_req_set_endtime(watch_req, req->ev_ctx, abs_timeout);
+ if (!ok) {
+ exit_server("tevent_req_set_endtime failed");
}
- if (!push_deferred_open_message_smb(req, request_time, timeout,
- open_rec->id, open_rec)) {
+ ok = push_deferred_open_message_smb(req, request_time, timeout,
+ open_rec->id, open_rec);
+ if (!ok) {
TALLOC_FREE(lck);
exit_server("push_deferred_open_message_smb failed");
}
NTSTATUS status;
bool ret;
- status = dbwrap_watched_watch_recv(req, talloc_tos(), NULL, NULL,
- NULL);
+ status = dbwrap_watched_watch_recv(req, NULL, NULL);
TALLOC_FREE(req);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(5, ("dbwrap_watched_watch_recv returned %s\n",
TALLOC_FREE(state);
}
+/**
+ * Actually attempt the kernel oplock polling open.
+ */
+
+static void kernel_oplock_poll_open_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ bool ok;
+ struct smb_request *req = (struct smb_request *)private_data;
+
+ ok = schedule_deferred_open_message_smb(req->xconn, req->mid);
+ if (!ok) {
+ exit_server("schedule_deferred_open_message_smb failed");
+ }
+ DBG_DEBUG("kernel_oplock_poll_open_timer fired. Retying open !\n");
+}
+
+/**
+ * Reschedule an open for 1 second from now, if not timed out.
+ **/
+static void setup_kernel_oplock_poll_open(struct timeval request_time,
+ struct smb_request *req,
+ struct file_id id)
+{
+
+ bool ok;
+ struct deferred_open_record *open_rec = NULL;
+ /* Maximum wait time. */
+ struct timeval timeout = timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0);
+
+ if (request_timed_out(request_time, timeout)) {
+ return;
+ }
+
+ open_rec = deferred_open_record_create(false, false, id);
+ if (open_rec == NULL) {
+ exit_server("talloc failed");
+ }
+
+ ok = push_deferred_open_message_smb(req,
+ request_time,
+ timeout,
+ id,
+ open_rec);
+ if (!ok) {
+ exit_server("push_deferred_open_message_smb failed");
+ }
+
+ /*
+ * As this timer event is owned by req, it will
+ * disappear if req it talloc_freed.
+ */
+ open_rec->te = tevent_add_timer(req->ev_ctx,
+ req,
+ timeval_current_ofs(1, 0),
+ kernel_oplock_poll_open_timer,
+ req);
+ if (open_rec->te == NULL) {
+ exit_server("tevent_add_timer failed");
+ }
+
+ DBG_DEBUG("poll request time [%s] mid [%" PRIu64 "] file_id [%s]\n",
+ timeval_string(talloc_tos(), &request_time, false),
+ req->mid,
+ file_id_string_tos(&id));
+}
/****************************************************************************
On overwrite open ensure that the attributes match.
static bool open_match_attributes(connection_struct *conn,
uint32_t old_dos_attr,
uint32_t new_dos_attr,
- mode_t existing_unx_mode,
mode_t new_unx_mode,
mode_t *returned_unx_mode)
{
}
DEBUG(10,("open_match_attributes: old_dos_attr = 0x%x, "
- "existing_unx_mode = 0%o, new_dos_attr = 0x%x "
+ "new_dos_attr = 0x%x "
"returned_unx_mode = 0%o\n",
(unsigned int)old_dos_attr,
- (unsigned int)existing_unx_mode,
(unsigned int)new_dos_attr,
(unsigned int)*returned_unx_mode ));
return;
}
- defer_open(lck, request_time, timeout, req, true, false, id);
+ defer_open(lck, request_time, timeout, req, true, id);
}
/****************************************************************************
Reschedule an open call that went asynchronous.
****************************************************************************/
+static void schedule_async_open_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ exit_server("async open timeout");
+}
+
static void schedule_async_open(struct timeval request_time,
struct smb_request *req)
{
- struct timeval timeout;
-
- timeout = timeval_set(20, 0);
+ struct deferred_open_record *open_rec = NULL;
+ struct timeval timeout = timeval_set(20, 0);
+ bool ok;
if (request_timed_out(request_time, timeout)) {
return;
}
- defer_open(NULL, request_time, timeout, req,
- false, true, (struct file_id){0});
+ open_rec = deferred_open_record_create(false, true, (struct file_id){0});
+ if (open_rec == NULL) {
+ exit_server("deferred_open_record_create failed");
+ }
+
+ ok = push_deferred_open_message_smb(req, request_time, timeout,
+ (struct file_id){0}, open_rec);
+ if (!ok) {
+ exit_server("push_deferred_open_message_smb failed");
+ }
+
+ open_rec->te = tevent_add_timer(req->ev_ctx,
+ req,
+ timeval_current_ofs(20, 0),
+ schedule_async_open_timer,
+ open_rec);
+ if (open_rec->te == NULL) {
+ exit_server("tevent_add_timer failed");
+ }
}
/****************************************************************************
(create_disposition == FILE_OVERWRITE_IF))) {
if (!open_match_attributes(conn, existing_dos_attributes,
new_dos_attributes,
- smb_fname->st.st_ex_mode,
unx_mode, &new_unx_mode)) {
- DEBUG(5,("open_file_ntcreate: attributes missmatch "
+ DEBUG(5,("open_file_ntcreate: attributes mismatch "
"for file %s (%x %x) (0%o, 0%o)\n",
smb_fname_str_dbg(smb_fname),
existing_dos_attributes,
flags2 &= ~(O_CREAT|O_TRUNC);
}
- if (first_open_attempt && lp_kernel_oplocks(SNUM(conn))) {
+ if (lp_kernel_oplocks(SNUM(conn))) {
/*
* With kernel oplocks the open breaking an oplock
* blocks until the oplock holder has given up the
- * oplock or closed the file. We prevent this by first
+ * oplock or closed the file. We prevent this by always
* trying to open the file with O_NONBLOCK (see "man
- * fcntl" on Linux). For the second try, triggered by
- * an oplock break response, we do not need this
- * anymore.
+ * fcntl" on Linux).
*
- * This is true under the assumption that only Samba
- * requests kernel oplocks. Once someone else like
- * NFSv4 starts to use that API, we will have to
- * modify this by communicating with the NFSv4 server.
+ * If a process that doesn't use the smbd open files
+ * database or communication methods holds a kernel
+ * oplock we must periodically poll for available open
+ * using O_NONBLOCK.
*/
flags2 |= O_NONBLOCK;
}
request_time = fsp->open_time;
}
+ if ((create_options & FILE_DELETE_ON_CLOSE) &&
+ (flags2 & O_CREAT) &&
+ !file_existed) {
+ /* Delete on close semantics for new files. */
+ status = can_set_delete_on_close(fsp,
+ new_dos_attributes);
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+ }
+
/*
* Ensure we pay attention to default ACLs on directories if required.
*/
lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
if (lck == NULL) {
- defer_open(NULL, request_time, timeval_set(0, 0),
- req, false, false, fsp->file_id);
- DEBUG(10, ("No share mode lock found after "
- "EWOULDBLOCK, retrying sync\n"));
+ /*
+ * No oplock from Samba around. Set up a poll every 1
+ * second to retry a non-blocking open until the time
+ * expires.
+ */
+ setup_kernel_oplock_poll_open(request_time,
+ req,
+ fsp->file_id);
+ DBG_DEBUG("No Samba oplock around after EWOULDBLOCK. "
+ "Retrying with poll\n");
return NT_STATUS_SHARING_VIOLATION;
}
}
/*
- * No oplock from Samba around. Immediately retry with
- * a blocking open.
+ * No oplock from Samba around. Set up a poll every 1
+ * second to retry a non-blocking open until the time
+ * expires.
*/
- defer_open(lck, request_time, timeval_set(0, 0), req,
- false, false, fsp->file_id);
+ setup_kernel_oplock_poll_open(request_time, req, fsp->file_id);
TALLOC_FREE(lck);
- DEBUG(10, ("No Samba oplock around after EWOULDBLOCK. "
- "Retrying sync\n"));
+ DBG_DEBUG("No Samba oplock around after EWOULDBLOCK. "
+ "Retrying with poll\n");
return NT_STATUS_SHARING_VIOLATION;
}
* in the open file db having the wrong dev/ino key.
*/
fd_close(fsp);
- DEBUG(1,("open_file_ntcreate: file %s - dev/ino mismatch. "
- "Old (dev=0x%llu, ino =0x%llu). "
- "New (dev=0x%llu, ino=0x%llu). Failing open "
- " with NT_STATUS_ACCESS_DENIED.\n",
- smb_fname_str_dbg(smb_fname),
- (unsigned long long)saved_stat.st_ex_dev,
- (unsigned long long)saved_stat.st_ex_ino,
- (unsigned long long)smb_fname->st.st_ex_dev,
- (unsigned long long)smb_fname->st.st_ex_ino));
+ DBG_WARNING("file %s - dev/ino mismatch. "
+ "Old (dev=%ju, ino=%ju). "
+ "New (dev=%ju, ino=%ju). Failing open "
+ "with NT_STATUS_ACCESS_DENIED.\n",
+ smb_fname_str_dbg(smb_fname),
+ (uintmax_t)saved_stat.st_ex_dev,
+ (uintmax_t)saved_stat.st_ex_ino,
+ (uintmax_t)smb_fname->st.st_ex_dev,
+ (uintmax_t)smb_fname->st.st_ex_ino);
return NT_STATUS_ACCESS_DENIED;
}
if (!request_timed_out(request_time, timeout)) {
defer_open(lck, request_time, timeout, req,
- false, false, id);
+ false, id);
}
}
/* Handle strange delete on close create semantics. */
if (create_options & FILE_DELETE_ON_CLOSE) {
+ if (!new_file_created) {
+ status = can_set_delete_on_close(fsp,
+ existing_dos_attributes);
- status = can_set_delete_on_close(fsp, new_dos_attributes);
-
- if (!NT_STATUS_IS_OK(status)) {
- /* Remember to delete the mode we just added. */
- del_share_mode(lck, fsp);
- TALLOC_FREE(lck);
- fd_close(fsp);
- return status;
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Remember to delete the mode we just added. */
+ del_share_mode(lck, fsp);
+ TALLOC_FREE(lck);
+ fd_close(fsp);
+ return status;
+ }
}
- /* Note that here we set the *inital* delete on close flag,
+ /* Note that here we set the *initial* delete on close flag,
not the regular one. The magic gets handled in close. */
fsp->initial_delete_on_close = True;
}
*/
if (!posix_open && new_file_created && !def_acl) {
-
- int saved_errno = errno; /* We might get ENOSYS in the next
- * call.. */
-
- if (SMB_VFS_FCHMOD_ACL(fsp, unx_mode) == -1 &&
- errno == ENOSYS) {
- errno = saved_errno; /* Ignore ENOSYS */
+ if (unx_mode != smb_fname->st.st_ex_mode) {
+ int ret = SMB_VFS_FCHMOD(fsp, unx_mode);
+ if (ret == -1) {
+ DBG_INFO("failed to reset "
+ "attributes of file %s to 0%o\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)unx_mode);
+ }
}
} else if (new_unx_mode) {
+ /*
+ * We only get here in the case of:
+ *
+ * a). Not a POSIX open.
+ * b). File already existed.
+ * c). File was overwritten.
+ * d). Requested DOS attributes didn't match
+ * the DOS attributes on the existing file.
+ *
+ * In that case new_unx_mode has been set
+ * equal to the calculated mode (including
+ * possible inheritance of the mode from the
+ * containing directory).
+ *
+ * Note this mode was calculated with the
+ * DOS attribute FILE_ATTRIBUTE_ARCHIVE added,
+ * so the mode change here is suitable for
+ * an overwritten file.
+ */
- int ret = -1;
-
- /* Attributes need changing. File already existed. */
-
- {
- int saved_errno = errno; /* We might get ENOSYS in the
- * next call.. */
- ret = SMB_VFS_FCHMOD_ACL(fsp, new_unx_mode);
-
- if (ret == -1 && errno == ENOSYS) {
- errno = saved_errno; /* Ignore ENOSYS */
- } else {
- DEBUG(5, ("open_file_ntcreate: reset "
- "attributes of file %s to 0%o\n",
- smb_fname_str_dbg(smb_fname),
- (unsigned int)new_unx_mode));
- ret = 0; /* Don't do the fchmod below. */
- }
- }
-
- if ((ret == -1) &&
- (SMB_VFS_FCHMOD(fsp, new_unx_mode) == -1))
- DEBUG(5, ("open_file_ntcreate: failed to reset "
+ if (new_unx_mode != smb_fname->st.st_ex_mode) {
+ int ret = SMB_VFS_FCHMOD(fsp, new_unx_mode);
+ if (ret == -1) {
+ DBG_INFO("failed to reset "
"attributes of file %s to 0%o\n",
smb_fname_str_dbg(smb_fname),
- (unsigned int)new_unx_mode));
+ (unsigned int)new_unx_mode);
+ }
+ }
}
{
if (lp_inherit_permissions(SNUM(conn))) {
inherit_access_posix_acl(conn, parent_dir,
- smb_dname->base_name, mode);
+ smb_dname, mode);
need_re_stat = true;
}
/* Change the owner if required. */
if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO) {
change_dir_owner_to_parent(conn, parent_dir,
- smb_dname->base_name,
+ smb_dname,
&smb_dname->st);
need_re_stat = true;
}
}
if (NT_STATUS_IS_OK(status)) {
- /* Note that here we set the *inital* delete on close flag,
+ /* Note that here we set the *initial* delete on close flag,
not the regular one. The magic gets handled in close. */
fsp->initial_delete_on_close = True;
}
/* If inheritable_components == false,
se_create_child_secdesc()
- creates a security desriptor with a NULL dacl
+ creates a security descriptor with a NULL dacl
entry, but with SEC_DESC_DACL_PRESENT. We need
to remove that flag. */
state.file_existed = VALID_STAT(fname->st);
if (state.file_existed) {
state.id = vfs_file_id_from_sbuf(conn, &fname->st);
- } else {
- memset(&state.id, '\0', sizeof(state.id));
}
status = leases_db_parse(&sconn->client->connections->smb2.client.guid,
for (j=0; j<d->num_share_modes; j++) {
struct share_mode_entry *e = &d->share_modes[j];
uint32_t e_lease_type = get_lease_type(d, e);
- struct share_mode_lease *l = NULL;
if (share_mode_stale_pid(d, j)) {
continue;
}
if (e->op_type == LEASE_OPLOCK) {
+ struct share_mode_lease *l = NULL;
l = &lck->data->leases[e->lease_idx];
if (!smb2_lease_key_equal(&l->lease_key,
lease_key)) {
continue;
}
- send_break_message(conn->sconn->msg_ctx, e,
+ send_break_message(conn->sconn->msg_ctx, &d->id, e,
SMB2_LEASE_NONE);
/*
* Send the breaks and then return
* SMB2_LEASE_NONE in the lease handle
* to cause them to acknowledge the
- * lease break. Consulatation with
+ * lease break. Consultation with
* Microsoft engineering confirmed
* this approach is safe.
*/
files_struct *fsp = NULL;
NTSTATUS status;
- DEBUG(10,("create_file_unixpath: access_mask = 0x%x "
+ DBG_DEBUG("create_file_unixpath: access_mask = 0x%x "
"file_attributes = 0x%x, share_access = 0x%x, "
"create_disposition = 0x%x create_options = 0x%x "
"oplock_request = 0x%x private_flags = 0x%x "
- "ea_list = 0x%p, sd = 0x%p, "
+ "ea_list = %p, sd = %p, "
"fname = %s\n",
(unsigned int)access_mask,
(unsigned int)file_attributes,
(unsigned int)create_options,
(unsigned int)oplock_request,
(unsigned int)private_flags,
- ea_list, sd, smb_fname_str_dbg(smb_fname)));
+ ea_list, sd, smb_fname_str_dbg(smb_fname));
if (create_options & FILE_OPEN_BY_FILE_ID) {
status = NT_STATUS_NOT_SUPPORTED;
if (lease != NULL) {
uint16_t epoch = lease->lease_epoch;
uint16_t version = lease->lease_version;
+
+ if (req == NULL) {
+ DBG_WARNING("Got lease on internal open\n");
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
status = lease_match(conn,
req,
&lease->lease_key,
goto fail;
}
+ /*
+ * Files or directories can't be opened DELETE_ON_CLOSE without
+ * delete access.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13358
+ */
+ if (create_options & FILE_DELETE_ON_CLOSE) {
+ if ((access_mask & DELETE_ACCESS) == 0) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ }
+
if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
&& is_ntfs_stream_smb_fname(smb_fname)
&& (!(private_flags & NTCREATEX_OPTIONS_PRIVATE_STREAM_DELETE))) {
uint32_t base_create_disposition;
struct smb_filename *smb_fname_base = NULL;
+ uint32_t base_privflags;
if (create_options & FILE_DIRECTORY_FILE) {
status = NT_STATUS_NOT_A_DIRECTORY;
}
}
+ base_privflags = NTCREATEX_OPTIONS_PRIVATE_STREAM_BASEOPEN;
+
/* Open the base file. */
status = create_file_unixpath(conn, NULL, smb_fname_base, 0,
FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE,
base_create_disposition,
- 0, 0, 0, NULL, 0, 0, NULL, NULL,
+ 0, 0, 0, NULL, 0,
+ base_privflags,
+ NULL, NULL,
&base_fsp, NULL);
TALLOC_FREE(smb_fname_base);
files_struct *dir_fsp;
char *parent_fname = NULL;
char *new_base_name = NULL;
- uint32_t ucf_flags = ((req != NULL && req->posix_pathnames) ?
- UCF_POSIX_PATHNAMES : 0);
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
NTSTATUS status;
if (root_dir_fid == 0 || !smb_fname) {
status = filename_convert(req,
conn,
- req->flags2 & FLAGS2_DFS_PATHNAMES,
new_base_name,
ucf_flags,
NULL,
+ NULL,
smb_fname_out);
if (!NT_STATUS_IS_OK(status)) {
goto out;
NTSTATUS status;
bool stream_name = false;
- DEBUG(10,("create_file: access_mask = 0x%x "
+ DBG_DEBUG("create_file: access_mask = 0x%x "
"file_attributes = 0x%x, share_access = 0x%x, "
"create_disposition = 0x%x create_options = 0x%x "
"oplock_request = 0x%x "
"private_flags = 0x%x "
- "root_dir_fid = 0x%x, ea_list = 0x%p, sd = 0x%p, "
+ "root_dir_fid = 0x%x, ea_list = %p, sd = %p, "
"fname = %s\n",
(unsigned int)access_mask,
(unsigned int)file_attributes,
(unsigned int)oplock_request,
(unsigned int)private_flags,
(unsigned int)root_dir_fid,
- ea_list, sd, smb_fname_str_dbg(smb_fname)));
+ ea_list, sd, smb_fname_str_dbg(smb_fname));
/*
* Calculate the filename from the root_dir_if if necessary.