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
#include "includes.h"
#include "system/filesys.h"
+#include "lib/util/server_id.h"
#include "printing.h"
#include "smbd/smbd.h"
#include "smbd/globals.h"
#include "messages.h"
#include "source3/lib/dbwrap/dbwrap_watch.h"
#include "locking/leases_db.h"
+#include "librpc/gen_ndr/ndr_leases_db.h"
extern const struct generic_mapping file_generic_mapping;
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;
};
/****************************************************************************
return NT_STATUS_OK;
}
- status = SMB_VFS_GET_NT_ACL(conn, smb_fname->base_name,
+ status = SMB_VFS_GET_NT_ACL(conn, smb_fname,
(SECINFO_OWNER |
SECINFO_GROUP |
SECINFO_DACL), talloc_tos(), &sd);
* Samba 3.6 and earlier granted execute access even
* if the ACL did not contain execute rights.
* Samba 4.0 is more correct and checks it.
- * The compatibilty mode allows to skip this check
+ * The compatibilty mode allows one to skip this check
* to smoothen upgrades.
*/
if (lp_acl_allow_execute_always(SNUM(conn))) {
return NT_STATUS_OK;
}
-static NTSTATUS check_parent_access(struct connection_struct *conn,
+NTSTATUS check_parent_access(struct connection_struct *conn,
struct smb_filename *smb_fname,
uint32_t access_mask)
{
char *parent_dir = NULL;
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 NT_STATUS_NO_MEMORY;
}
+ parent_smb_fname = synthetic_smb_fname(talloc_tos(),
+ parent_dir,
+ NULL,
+ NULL,
+ smb_fname->flags);
+ if (parent_smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
if (get_current_uid(conn) == (uid_t)0) {
/* I'm sorry sir, I didn't know you were root... */
DEBUG(10,("check_parent_access: root override "
}
status = SMB_VFS_GET_NT_ACL(conn,
- parent_dir,
+ parent_smb_fname,
SECINFO_DACL,
talloc_tos(),
&parent_sd);
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_open || !lp_follow_symlinks(SNUM(conn))) {
+ 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;
struct smb_filename *smb_fname_parent;
int ret;
- smb_fname_parent = synthetic_smb_fname(talloc_tos(), inherit_from_dir,
- NULL, NULL);
+ smb_fname_parent = synthetic_smb_fname(talloc_tos(),
+ inherit_from_dir,
+ NULL,
+ NULL,
+ 0);
if (smb_fname_parent == NULL) {
return;
}
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;
- smb_fname_parent = synthetic_smb_fname(ctx, inherit_from_dir,
- NULL, NULL);
+ smb_fname_parent = synthetic_smb_fname(ctx,
+ inherit_from_dir,
+ NULL,
+ NULL,
+ 0);
if (smb_fname_parent == NULL) {
return NT_STATUS_NO_MEMORY;
}
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;
}
- smb_fname_cwd = synthetic_smb_fname(ctx, ".", NULL, NULL);
+ smb_fname_cwd = synthetic_smb_fname(ctx, ".", NULL, NULL, 0);
if (smb_fname_cwd == NULL) {
status = NT_STATUS_NO_MEMORY;
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;
}
become_root();
- ret = SMB_VFS_LCHOWN(conn, ".", smb_fname_parent->st.st_ex_uid,
- (gid_t)-1);
+ ret = SMB_VFS_LCHOWN(conn,
+ smb_fname_cwd,
+ smb_fname_parent->st.st_ex_uid,
+ (gid_t)-1);
unbecome_root();
if (ret == -1) {
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;
bool *file_created)
{
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ NTSTATUS retry_status;
bool file_existed = VALID_STAT(fsp->fsp_name->st);
+ int curr_flags;
*file_created = false;
* we can never call O_CREAT without O_EXCL. So if
* we think the file existed, try without O_CREAT|O_EXCL.
* If we think the file didn't exist, try with
- * O_CREAT|O_EXCL. Keep bouncing between these two
- * requests until either the file is created, or
- * opened. Either way, we keep going until we get
- * a returnable result (error, or open/create).
+ * O_CREAT|O_EXCL.
+ *
+ * The big problem here is dangling symlinks. Opening
+ * without O_NOFOLLOW means both bad symlink
+ * and missing path return -1, ENOENT from open(). As POSIX
+ * is pathname based it's not possible to tell
+ * the difference between these two cases in a
+ * non-racy way, so change to try only two attempts before
+ * giving up.
+ *
+ * We don't have this problem for the O_NOFOLLOW
+ * case as it just returns NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * mapped from the ELOOP POSIX error.
*/
- while(1) {
- int curr_flags = flags;
+ curr_flags = flags;
- if (file_existed) {
- /* Just try open, do not create. */
- curr_flags &= ~(O_CREAT);
- status = fd_open(conn, fsp, curr_flags, mode);
- if (NT_STATUS_EQUAL(status,
- NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
- /*
- * Someone deleted it in the meantime.
- * Retry with O_EXCL.
- */
- file_existed = false;
- DEBUG(10,("fd_open_atomic: file %s existed. "
- "Retry.\n",
- smb_fname_str_dbg(fsp->fsp_name)));
- continue;
- }
- } else {
- /* Try create exclusively, fail if it exists. */
- curr_flags |= O_EXCL;
- status = fd_open(conn, fsp, curr_flags, mode);
- if (NT_STATUS_EQUAL(status,
- NT_STATUS_OBJECT_NAME_COLLISION)) {
- /*
- * Someone created it in the meantime.
- * Retry without O_CREAT.
- */
- file_existed = true;
- DEBUG(10,("fd_open_atomic: file %s "
- "did not exist. Retry.\n",
- smb_fname_str_dbg(fsp->fsp_name)));
- continue;
- }
- if (NT_STATUS_IS_OK(status)) {
- /*
- * Here we've opened with O_CREAT|O_EXCL
- * and got success. We *know* we created
- * this file.
- */
- *file_created = true;
- }
+ if (file_existed) {
+ curr_flags &= ~(O_CREAT);
+ retry_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ } else {
+ curr_flags |= O_EXCL;
+ retry_status = NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ status = fd_open(conn, fsp, curr_flags, mode);
+ if (NT_STATUS_IS_OK(status)) {
+ if (!file_existed) {
+ *file_created = true;
}
- /* Create is done, or failed. */
- break;
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_EQUAL(status, retry_status)) {
+ return status;
+ }
+
+ curr_flags = flags;
+
+ /*
+ * Keep file_existed up to date for clarity.
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ file_existed = false;
+ curr_flags |= O_EXCL;
+ DBG_DEBUG("file %s did not exist. Retry.\n",
+ smb_fname_str_dbg(fsp->fsp_name));
+ } else {
+ file_existed = true;
+ curr_flags &= ~(O_CREAT);
+ DBG_DEBUG("file %s existed. Retry.\n",
+ smb_fname_str_dbg(fsp->fsp_name));
}
+
+ status = fd_open(conn, fsp, curr_flags, mode);
+
+ if (NT_STATUS_IS_OK(status) && (!file_existed)) {
+ *file_created = true;
+ }
+
return status;
}
const char *parent_dir,
int flags,
mode_t unx_mode,
- uint32 access_mask, /* client requested access mask. */
- uint32 open_access_mask, /* what we're actually using in the open. */
+ uint32_t access_mask, /* client requested access mask. */
+ uint32_t open_access_mask, /* what we're actually using in the open. */
bool *p_file_created)
{
struct smb_filename *smb_fname = fsp->fsp_name;
wild = smb_fname->base_name;
}
if ((local_flags & O_CREAT) && !file_existed &&
+ !(fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES) &&
ms_has_wild(wild)) {
return NT_STATUS_OBJECT_NAME_INVALID;
}
return status;
}
+ if (local_flags & O_NONBLOCK) {
+ /*
+ * GPFS can return ETIMEDOUT for pread on
+ * nonblocking file descriptors when files
+ * migrated to tape need to be recalled. I
+ * could imagine this happens elsehwere
+ * too. With blocking file descriptors this
+ * does not happen.
+ */
+ ret = set_blocking(fsp->fh->fd, true);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_WARNING("Could not set fd to blocking: "
+ "%s\n", strerror(errno));
+ fd_close(fsp);
+ return status;
+ }
+ }
+
ret = SMB_VFS_FSTAT(fsp, &smb_fname->st);
if (ret == -1) {
/* If we have an fd, this stat should succeed. */
/* 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;
}
/* Change the owner if required. */
- if (lp_inherit_owner(SNUM(conn))) {
+ if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO) {
change_file_owner_to_parent(conn, parent_dir,
fsp);
need_re_stat = true;
access_mask);
if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
- fsp->posix_open &&
+ (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) &&
S_ISLNK(smb_fname->st.st_ex_mode)) {
/* This is a POSIX stat open for delete
* or rename on a symlink that points
****************************************************************************/
static bool share_conflict(struct share_mode_entry *entry,
- uint32 access_mask,
- uint32 share_access)
+ uint32_t access_mask,
+ uint32_t share_access)
{
DEBUG(10,("share_conflict: entry->access_mask = 0x%x, "
"entry->share_access = 0x%x, "
#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");
}
- if (((uint16)fsp->oplock_type) != share_entry->op_type) {
+ if (((uint16_t)fsp->oplock_type) != share_entry->op_type) {
goto panic;
}
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",
}
#endif
-bool is_stat_open(uint32 access_mask)
+bool is_stat_open(uint32_t access_mask)
{
const uint32_t stat_open_bits =
(SYNCHRONIZE_ACCESS|
static NTSTATUS open_mode_check(connection_struct *conn,
struct share_mode_lock *lck,
- uint32 access_mask,
- uint32 share_access)
+ uint32_t access_mask,
+ uint32_t share_access)
{
- int i;
+ uint32_t i;
if(lck->data->num_share_modes == 0) {
return NT_STATUS_OK;
#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
* our client.
*/
-static NTSTATUS send_break_message(struct messaging_context *msg_ctx,
- const struct share_mode_entry *exclusive,
- uint16_t break_to)
+NTSTATUS send_break_message(struct messaging_context *msg_ctx,
+ 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];
+ struct server_id_buf tmp;
DEBUG(10, ("Sending break request to PID %s\n",
- procid_str_static(&exclusive->pid)));
+ 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 */
/*
- * This is a cut from uint32 to uint16, but so far only the lower 3
+ * This is a cut from uint32_t to uint16_t, but so far only the lower 3
* bits (LEASE_WRITE/HANDLE/READ are used anyway.
*/
SSVAL(msg,OP_BREAK_MSG_OP_TYPE_OFFSET, break_to);
status = messaging_send_buf(msg_ctx, exclusive->pid,
MSG_SMB_BREAK_REQUEST,
- (uint8 *)msg, sizeof(msg));
+ (uint8_t *)msg, sizeof(msg));
if (!NT_STATUS_IS_OK(status)) {
DEBUG(3, ("Could not send oplock break message: %s\n",
nt_errstr(status)));
/*
* we'll decide about SMB2_LEASE_READ later.
*
- * Maybe the break will be defered
+ * Maybe the break will be deferred
*/
break_to &= ~SMB2_LEASE_HANDLE;
}
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;
}
.epoch = fsp->lease->lease.lease_epoch,
};
- status = leases_db_add(client_guid, &lease->lease_key,
- &fsp->file_id, fsp->fsp_name->base_name,
+ status = leases_db_add(client_guid,
+ &lease->lease_key,
+ &fsp->file_id,
+ fsp->conn->connectpath,
+ fsp->fsp_name->base_name,
fsp->fsp_name->stream_name);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(10, ("%s: leases_db_add failed: %s\n", __func__,
return (timeval_compare(&end_time, &now) < 0);
}
+static struct deferred_open_record *deferred_open_record_create(
+ bool delayed_for_oplocks,
+ bool async_open,
+ struct file_id id)
+{
+ struct deferred_open_record *record = NULL;
+
+ record = talloc(NULL, struct deferred_open_record);
+ if (record == NULL) {
+ return NULL;
+ }
+
+ *record = (struct deferred_open_record) {
+ .delayed_for_oplocks = delayed_for_oplocks,
+ .async_open = async_open,
+ .id = id,
+ };
+
+ return record;
+}
+
struct defer_open_state {
struct smbXsrv_connection *xconn;
uint64_t mid;
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,
- struct deferred_open_record *state)
+ bool delayed_for_oplocks,
+ struct file_id id)
{
- struct deferred_open_record *open_rec;
+ 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);
- DEBUG(10,("defer_open_sharing_error: time [%u.%06u] adding deferred "
- "open entry for mid %llu\n",
- (unsigned int)request_time.tv_sec,
- (unsigned int)request_time.tv_usec,
- (unsigned long long)req->mid));
+ DBG_DEBUG("request time [%s] timeout [%s] mid [%" PRIu64 "] "
+ "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",
+ file_id_string_tos(&id));
- open_rec = talloc(NULL, struct deferred_open_record);
+ open_rec = deferred_open_record_create(delayed_for_oplocks,
+ false,
+ id);
if (open_rec == NULL) {
TALLOC_FREE(lck);
exit_server("talloc failed");
}
- *open_rec = *state;
-
- 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_record_watch_send(
- watch_state, req->sconn->ev_ctx, lck->data->record,
- req->sconn->msg_ctx);
- 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->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);
- ret = tevent_req_set_endtime(
- watch_req, req->sconn->ev_ctx,
- timeval_sum(&request_time, &timeout));
- SMB_ASSERT(ret);
+ ok = tevent_req_set_endtime(watch_req, req->sconn->ev_ctx, abs_timeout);
+ if (!ok) {
+ exit_server("tevent_req_set_endtime failed");
}
- if (!push_deferred_open_message_smb(req, request_time, timeout,
- state->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_record_watch_recv(req, talloc_tos(), NULL);
+ status = dbwrap_watched_watch_recv(req, NULL, NULL);
TALLOC_FREE(req);
if (!NT_STATUS_IS_OK(status)) {
- DEBUG(5, ("dbwrap_record_watch_recv returned %s\n",
+ DEBUG(5, ("dbwrap_watched_watch_recv returned %s\n",
nt_errstr(status)));
/*
* Even if it failed, retry anyway. TODO: We need a way to
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->sconn->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 old_dos_attr,
- uint32 new_dos_attr,
+ 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)
{
- uint32 noarch_old_dos_attr, noarch_new_dos_attr;
+ uint32_t noarch_old_dos_attr, noarch_new_dos_attr;
noarch_old_dos_attr = (old_dos_attr & ~FILE_ATTRIBUTE_ARCHIVE);
noarch_new_dos_attr = (new_dos_attr & ~FILE_ATTRIBUTE_ARCHIVE);
files_struct *fsp_to_dup_into,
const struct smb_filename *smb_fname,
struct file_id id,
- uint16 file_pid,
+ uint16_t file_pid,
uint64_t vuid,
- uint32 access_mask,
- uint32 share_access,
- uint32 create_options)
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_options)
{
files_struct *fsp;
struct timeval request_time,
struct smb_request *req)
{
- struct deferred_open_record state;
-
/* This is a relative time, added to the absolute
request_time value to get the absolute timeout time.
Note that if this is the second or greater time we enter
timeout = timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0);
- /* Nothing actually uses state.delayed_for_oplocks
- but it's handy to differentiate in debug messages
- between a 30 second delay due to oplock break, and
- a 1 second delay for share mode conflicts. */
-
- state.delayed_for_oplocks = True;
- state.async_open = false;
- state.id = id;
-
- if (!request_timed_out(request_time, timeout)) {
- defer_open(lck, request_time, timeout, req, &state);
+ if (request_timed_out(request_time, timeout)) {
+ return;
}
+
+ 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 deferred_open_record state;
- struct timeval timeout;
+ struct deferred_open_record *open_rec = NULL;
+ struct timeval timeout = timeval_set(20, 0);
+ bool ok;
- timeout = timeval_set(20, 0);
+ if (request_timed_out(request_time, timeout)) {
+ return;
+ }
- ZERO_STRUCT(state);
- state.delayed_for_oplocks = false;
- state.async_open = true;
+ open_rec = deferred_open_record_create(false, true, (struct file_id){0});
+ if (open_rec == NULL) {
+ exit_server("deferred_open_record_create failed");
+ }
- if (!request_timed_out(request_time, timeout)) {
- defer_open(NULL, request_time, timeout, req, &state);
+ 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->sconn->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");
}
}
return NT_STATUS_OK;
}
- status = SMB_VFS_GET_NT_ACL(conn, smb_fname->base_name,
+ status = SMB_VFS_GET_NT_ACL(conn, smb_fname,
(SECINFO_OWNER |
SECINFO_GROUP |
SECINFO_DACL),
uint32_t orig_access_mask = access_mask;
uint32_t rejected_share_access;
+ if (access_mask & SEC_MASK_INVALID) {
+ DBG_DEBUG("access_mask [%8x] contains invalid bits\n",
+ access_mask);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
/*
* Convert GENERIC bits to specific bits.
*/
}
static int calculate_open_access_flags(uint32_t access_mask,
- int oplock_request,
uint32_t private_flags)
{
bool need_write, need_read;
static NTSTATUS open_file_ntcreate(connection_struct *conn,
struct smb_request *req,
- uint32 access_mask, /* access bits (FILE_READ_DATA etc.) */
- uint32 share_access, /* share constants (FILE_SHARE_READ etc) */
- uint32 create_disposition, /* FILE_OPEN_IF etc. */
- uint32 create_options, /* options such as delete on close. */
- uint32 new_dos_attributes, /* attributes used for new file. */
+ uint32_t access_mask, /* access bits (FILE_READ_DATA etc.) */
+ uint32_t share_access, /* share constants (FILE_SHARE_READ etc) */
+ uint32_t create_disposition, /* FILE_OPEN_IF etc. */
+ uint32_t create_options, /* options such as delete on close. */
+ uint32_t new_dos_attributes, /* attributes used for new file. */
int oplock_request, /* internal Samba oplock codes. */
struct smb2_lease *lease,
/* Information (FILE_EXISTS etc.) */
mode_t new_unx_mode = (mode_t)0;
mode_t unx_mode = (mode_t)0;
int info;
- uint32 existing_dos_attributes = 0;
+ uint32_t existing_dos_attributes = 0;
struct timeval request_time = timeval_zero();
struct share_mode_lock *lck = NULL;
- uint32 open_access_mask = access_mask;
+ uint32_t open_access_mask = access_mask;
NTSTATUS status;
char *parent_dir;
SMB_STRUCT_STAT saved_stat = smb_fname->st;
if (!posix_open) {
new_dos_attributes &= SAMBA_ATTRIBUTES_MASK;
if (file_existed) {
- existing_dos_attributes = dos_mode(conn, smb_fname);
+ /*
+ * Only use strored DOS attributes for checks
+ * against requested attributes (below via
+ * open_match_attributes()), cf bug #11992
+ * for details. -slow
+ */
+ uint32_t attr = 0;
+
+ status = SMB_VFS_GET_DOS_ATTRIBUTES(conn, smb_fname, &attr);
+ if (NT_STATUS_IS_OK(status)) {
+ existing_dos_attributes = attr;
+ }
}
}
}
/* this is for OS/2 long file names - say we don't support them */
- if (!lp_posix_pathnames() && strstr(smb_fname->base_name,".+,;=[].")) {
+ if (req != NULL && !req->posix_pathnames &&
+ strstr(smb_fname->base_name,".+,;=[].")) {
/* OS/2 Workplace shell fix may be main code stream in a later
* release. */
DEBUG(5,("open_file_ntcreate: OS/2 long filenames are not "
* mean the same thing under DOS and Unix.
*/
- flags = calculate_open_access_flags(access_mask, oplock_request,
- private_flags);
+ flags = calculate_open_access_flags(access_mask, private_flags);
/*
* Currently we only look at FILE_WRITE_THROUGH for create options.
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;
}
fsp->access_mask = open_access_mask; /* We change this to the
* requested access_mask after
* the open is done. */
- fsp->posix_open = posix_open;
+ if (posix_open) {
+ fsp->posix_flags |= FSP_POSIX_FLAGS_ALL;
+ }
if (timeval_is_zero(&request_time)) {
request_time = fsp->open_time;
open_access_mask, &new_file_created);
if (NT_STATUS_EQUAL(fsp_open, NT_STATUS_NETWORK_BUSY)) {
- struct deferred_open_record state;
+ bool delay;
/*
- * EWOULDBLOCK/EAGAIN maps to NETWORK_BUSY.
+ * This handles the kernel oplock case:
+ *
+ * the file has an active kernel oplock and the open() returned
+ * EWOULDBLOCK/EAGAIN which maps to NETWORK_BUSY.
+ *
+ * "Samba locking.tdb oplocks" are handled below after acquiring
+ * the sharemode lock with get_share_mode_lock().
*/
if (file_existed && S_ISFIFO(fsp->fsp_name->st.st_ex_mode)) {
DEBUG(10, ("FIFO busy\n"));
lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
if (lck == NULL) {
- state.delayed_for_oplocks = false;
- state.async_open = false;
- state.id = fsp->file_id;
- defer_open(NULL, request_time, timeval_set(0, 0),
- req, &state);
- 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;
}
smb_panic("validate_oplock_types failed");
}
- if (delay_for_oplock(fsp, 0, lease, lck, false,
- create_disposition, first_open_attempt)) {
+ delay = delay_for_oplock(fsp, 0, lease, lck, false,
+ create_disposition,
+ first_open_attempt);
+ if (delay) {
schedule_defer_open(lck, fsp->file_id, request_time,
req);
TALLOC_FREE(lck);
}
/*
- * 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.
*/
- state.delayed_for_oplocks = false;
- state.async_open = false;
- state.id = fsp->file_id;
- defer_open(lck, request_time, timeval_set(0, 0), req, &state);
+ 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;
}
file_existed = true;
}
- if ((req != NULL) &&
- delay_for_oplock(
- fsp, oplock_request, lease, lck,
- NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION),
- create_disposition, first_open_attempt)) {
- schedule_defer_open(lck, fsp->file_id, request_time, req);
- TALLOC_FREE(lck);
- fd_close(fsp);
- return NT_STATUS_SHARING_VIOLATION;
+ if (req != NULL) {
+ /*
+ * Handle oplocks, deferring the request if delay_for_oplock()
+ * triggered a break message and we have to wait for the break
+ * response.
+ */
+ bool delay;
+ bool sharing_violation = NT_STATUS_EQUAL(
+ status, NT_STATUS_SHARING_VIOLATION);
+
+ delay = delay_for_oplock(fsp, oplock_request, lease, lck,
+ sharing_violation,
+ create_disposition,
+ first_open_attempt);
+ if (delay) {
+ schedule_defer_open(lck, fsp->file_id,
+ request_time, req);
+ TALLOC_FREE(lck);
+ fd_close(fsp);
+ return NT_STATUS_SHARING_VIOLATION;
+ }
}
if (!NT_STATUS_IS_OK(status)) {
- uint32 can_access_mask;
+ uint32_t can_access_mask;
bool can_access = True;
SMB_ASSERT(NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION));
!conn->sconn->using_smb2 &&
lp_defer_sharing_violations()) {
struct timeval timeout;
- struct deferred_open_record state;
int timeout_usecs;
/* this is a hack to speed up torture tests
timeout = timeval_set(0, timeout_usecs);
- /* Nothing actually uses state.delayed_for_oplocks
- but it's handy to differentiate in debug messages
- between a 30 second delay due to oplock break, and
- a 1 second delay for share mode conflicts. */
-
- state.delayed_for_oplocks = False;
- state.async_open = false;
- state.id = id;
-
- if ((req != NULL)
- && !request_timed_out(request_time,
- timeout)) {
- defer_open(lck, request_time, timeout,
- req, &state);
+ if (!request_timed_out(request_time, timeout)) {
+ defer_open(lck, request_time, timeout, req,
+ false, id);
}
}
/* Delete streams if create_disposition requires it */
if (!new_file_created && clear_ads(create_disposition) &&
!is_ntfs_stream_smb_fname(smb_fname)) {
- status = delete_all_streams(conn, smb_fname->base_name);
+ status = delete_all_streams(conn, smb_fname);
if (!NT_STATUS_IS_OK(status)) {
TALLOC_FREE(lck);
fd_close(fsp);
if (fsp->fh->fd != -1 && lp_kernel_share_modes(SNUM(conn))) {
int ret_flock;
+ /*
+ * Beware: streams implementing VFS modules may
+ * implement streams in a way that fsp will have the
+ * basefile open in the fsp fd, so lacking a distinct
+ * fd for the stream kernel_flock will apply on the
+ * basefile which is wrong. The actual check is
+ * deffered to the VFS module implementing the
+ * kernel_flock call.
+ */
ret_flock = SMB_VFS_KERNEL_FLOCK(fsp, share_access, access_mask);
if(ret_flock == -1 ){
return NT_STATUS_SHARING_VIOLATION;
}
+
+ fsp->kernel_share_modes_taken = true;
}
/*
if (file_existed) {
/*
- * stat opens on existing files don't get oplocks or leases.
+ * stat opens on existing files don't get oplocks.
+ * They can get leases.
*
* Note that we check for stat open on the *open_access_mask*,
* i.e. the access mask we actually used to do the open,
* FILE_OVERWRITE and FILE_OVERWRITE_IF add in O_TRUNC,
* which adds FILE_WRITE_DATA to open_access_mask.
*/
- if (is_stat_open(open_access_mask)) {
- if (lease) {
- lease->lease_state = SMB2_LEASE_NONE;
- } else {
- oplock_request = NO_OPLOCK;
- }
+ if (is_stat_open(open_access_mask) && lease == NULL) {
+ oplock_request = NO_OPLOCK;
}
}
}
if (info != FILE_WAS_OPENED) {
- /* Files should be initially set as archive */
- if (lp_map_archive(SNUM(conn)) ||
+ /* Overwritten files should be initially set as archive */
+ if ((info == FILE_WAS_OVERWRITTEN && lp_map_archive(SNUM(conn))) ||
lp_store_dos_attributes(SNUM(conn))) {
if (!posix_open) {
if (file_set_dosmode(conn, smb_fname,
static NTSTATUS mkdir_internal(connection_struct *conn,
struct smb_filename *smb_dname,
- uint32 file_attributes)
+ uint32_t file_attributes)
{
mode_t mode;
char *parent_dir = NULL;
return status;
}
- if (SMB_VFS_MKDIR(conn, smb_dname->base_name, mode) != 0) {
+ if (SMB_VFS_MKDIR(conn, smb_dname, mode) != 0) {
return map_nt_error_from_unix(errno);
}
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;
}
*/
if ((mode & ~(S_IRWXU|S_IRWXG|S_IRWXO)) &&
(mode & ~smb_dname->st.st_ex_mode)) {
- SMB_VFS_CHMOD(conn, smb_dname->base_name,
+ SMB_VFS_CHMOD(conn, smb_dname,
(smb_dname->st.st_ex_mode |
(mode & ~smb_dname->st.st_ex_mode)));
need_re_stat = true;
}
/* Change the owner if required. */
- if (lp_inherit_owner(SNUM(conn))) {
+ 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;
}
static NTSTATUS open_directory(connection_struct *conn,
struct smb_request *req,
struct smb_filename *smb_dname,
- uint32 access_mask,
- uint32 share_access,
- uint32 create_disposition,
- uint32 create_options,
- uint32 file_attributes,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
int *pinfo,
files_struct **result)
{
nt_errstr(status)));
return status;
}
+
+ /*
+ * If mkdir_internal() returned
+ * NT_STATUS_OBJECT_NAME_COLLISION
+ * we still must lstat the path.
+ */
+
+ if (SMB_VFS_LSTAT(conn, smb_dname)
+ == -1) {
+ DEBUG(2, ("Could not stat "
+ "directory '%s' just "
+ "opened: %s\n",
+ smb_fname_str_dbg(
+ smb_dname),
+ strerror(errno)));
+ return map_nt_error_from_unix(
+ errno);
+ }
+
info = FILE_WAS_OPENED;
}
}
fsp->oplock_type = NO_OPLOCK;
fsp->sent_oplock_break = NO_BREAK_SENT;
fsp->is_directory = True;
- fsp->posix_open = (file_attributes & FILE_FLAG_POSIX_SEMANTICS) ? True : False;
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ fsp->posix_flags |= FSP_POSIX_FLAGS_ALL;
+ }
status = fsp_set_smb_fname(fsp, smb_dname);
if (!NT_STATUS_IS_OK(status)) {
file_free(req, fsp);
return status;
}
- /* Ensure there was no race condition. */
- if (!check_same_stat(&smb_dname->st, &fsp->fsp_name->st)) {
+ if(!S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ DEBUG(5,("open_directory: %s is not a directory !\n",
+ smb_fname_str_dbg(smb_dname)));
+ fd_close(fsp);
+ file_free(req, fsp);
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ /* Ensure there was no race condition. We need to check
+ * dev/inode but not permissions, as these can change
+ * legitimately */
+ if (!check_same_dev_ino(&smb_dname->st, &fsp->fsp_name->st)) {
DEBUG(5,("open_directory: stat struct differs for "
"directory %s.\n",
smb_fname_str_dbg(smb_dname)));
NULL, /* sd */
NULL, /* ea_list */
&fsp, /* result */
- NULL); /* pinfo */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
if (NT_STATUS_IS_OK(status)) {
close_file(req, fsp, NORMAL_CLOSE);
stream_name = NULL;
}
- smb_fname = synthetic_smb_fname(talloc_tos(), base_name,
- stream_name, NULL);
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ base_name,
+ stream_name,
+ NULL,
+ 0);
if (smb_fname == NULL) {
return;
}
* If that works, delete them all by setting the delete on close and close.
*/
-NTSTATUS open_streams_for_delete(connection_struct *conn,
- const char *fname)
+static NTSTATUS open_streams_for_delete(connection_struct *conn,
+ const struct smb_filename *smb_fname)
{
struct stream_struct *stream_info = NULL;
files_struct **streams = NULL;
TALLOC_CTX *frame = talloc_stackframe();
NTSTATUS status;
- status = vfs_streaminfo(conn, NULL, fname, talloc_tos(),
+ status = vfs_streaminfo(conn, NULL, smb_fname, talloc_tos(),
&num_streams, &stream_info);
if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)
}
for (i=0; i<num_streams; i++) {
- struct smb_filename *smb_fname;
+ struct smb_filename *smb_fname_cp;
if (strequal(stream_info[i].name, "::$DATA")) {
streams[i] = NULL;
continue;
}
- smb_fname = synthetic_smb_fname(
- talloc_tos(), fname, stream_info[i].name, NULL);
- if (smb_fname == NULL) {
+ smb_fname_cp = synthetic_smb_fname(talloc_tos(),
+ smb_fname->base_name,
+ stream_info[i].name,
+ NULL,
+ (smb_fname->flags &
+ ~SMB_FILENAME_POSIX_PATH));
+ if (smb_fname_cp == NULL) {
status = NT_STATUS_NO_MEMORY;
goto fail;
}
- if (SMB_VFS_STAT(conn, smb_fname) == -1) {
+ if (SMB_VFS_STAT(conn, smb_fname_cp) == -1) {
DEBUG(10, ("Unable to stat stream: %s\n",
- smb_fname_str_dbg(smb_fname)));
+ smb_fname_str_dbg(smb_fname_cp)));
}
status = SMB_VFS_CREATE_FILE(
conn, /* conn */
NULL, /* req */
0, /* root_dir_fid */
- smb_fname, /* fname */
+ smb_fname_cp, /* fname */
DELETE_ACCESS, /* access_mask */
(FILE_SHARE_READ | /* share_access */
FILE_SHARE_WRITE | FILE_SHARE_DELETE),
NULL, /* sd */
NULL, /* ea_list */
&streams[i], /* result */
- NULL); /* pinfo */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
if (!NT_STATUS_IS_OK(status)) {
DEBUG(10, ("Could not open stream %s: %s\n",
- smb_fname_str_dbg(smb_fname),
+ smb_fname_str_dbg(smb_fname_cp),
nt_errstr(status)));
- TALLOC_FREE(smb_fname);
+ TALLOC_FREE(smb_fname_cp);
break;
}
- TALLOC_FREE(smb_fname);
+ TALLOC_FREE(smb_fname_cp);
}
/*
const struct dom_sid *group_sid = NULL;
uint32_t security_info_sent = (SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL);
struct security_token *token = fsp->conn->session_info->security_token;
- bool inherit_owner = lp_inherit_owner(SNUM(fsp->conn));
+ bool inherit_owner =
+ (lp_inherit_owner(SNUM(fsp->conn)) == INHERIT_OWNER_WINDOWS_AND_UNIX);
bool inheritable_components = false;
bool try_builtin_administrators = false;
const struct dom_sid *BA_U_sid = NULL;
const struct dom_sid *SY_U_sid = NULL;
const struct dom_sid *SY_G_sid = NULL;
size_t size = 0;
+ struct smb_filename *parent_smb_fname = NULL;
if (!parent_dirname(frame, fsp->fsp_name->base_name, &parent_name, NULL)) {
TALLOC_FREE(frame);
return NT_STATUS_NO_MEMORY;
}
+ parent_smb_fname = synthetic_smb_fname(talloc_tos(),
+ parent_name,
+ NULL,
+ NULL,
+ fsp->fsp_name->flags);
+
+ if (parent_smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
status = SMB_VFS_GET_NT_ACL(fsp->conn,
- parent_name,
+ parent_smb_fname,
(SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL),
frame,
&parent_desc);
* used for a different file name.
*/
-struct lease_fname_match_state {
+struct lease_match_state {
/* Input parameters. */
+ TALLOC_CTX *mem_ctx;
+ const char *servicepath;
const struct smb_filename *fname;
bool file_existed;
struct file_id id;
NTSTATUS match_status;
};
-static void lease_fname_match_parser(
- uint32_t num_file_ids,
- struct file_id *ids, const char *filename, const char *stream_name,
+/*************************************************************
+ File doesn't exist but this lease key+guid is already in use.
+
+ This is only allowable in the dynamic share case where the
+ service path must be different.
+
+ There is a small race condition here in the multi-connection
+ case where a client sends two create calls on different connections,
+ where the file doesn't exist and one smbd creates the leases_db
+ entry first, but this will get fixed by the multichannel cleanup
+ when all identical client_guids get handled by a single smbd.
+**************************************************************/
+
+static void lease_match_parser_new_file(
+ uint32_t num_files,
+ const struct leases_db_file *files,
+ struct lease_match_state *state)
+{
+ uint32_t i;
+
+ for (i = 0; i < num_files; i++) {
+ const struct leases_db_file *f = &files[i];
+ if (strequal(state->servicepath, f->servicepath)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ return;
+ }
+ }
+
+ /* Dynamic share case. Break leases on all other files. */
+ state->match_status = leases_db_copy_file_ids(state->mem_ctx,
+ num_files,
+ files,
+ &state->ids);
+ if (!NT_STATUS_IS_OK(state->match_status)) {
+ return;
+ }
+
+ state->num_file_ids = num_files;
+ state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ return;
+}
+
+static void lease_match_parser(
+ uint32_t num_files,
+ const struct leases_db_file *files,
void *private_data)
{
- struct lease_fname_match_state *state =
- (struct lease_fname_match_state *)private_data;
+ struct lease_match_state *state =
+ (struct lease_match_state *)private_data;
+ uint32_t i;
- if (!strequal(filename, state->fname->base_name) ||
- !strequal(stream_name, state->fname->stream_name))
- {
- /* Names don't match lease key. */
- state->match_status = NT_STATUS_INVALID_PARAMETER;
+ if (!state->file_existed) {
+ /*
+ * Deal with name mismatch or
+ * possible dynamic share case separately
+ * to make code clearer.
+ */
+ lease_match_parser_new_file(num_files,
+ files,
+ state);
return;
}
- if (state->file_existed &&
- num_file_ids == 1 &&
- file_id_equal(&ids[0],&state->id))
- {
- /* Common case - non-dynamic share. We're ok.. */
- state->match_status = NT_STATUS_OK;
+ /* File existed. */
+ state->match_status = NT_STATUS_OK;
+
+ for (i = 0; i < num_files; i++) {
+ const struct leases_db_file *f = &files[i];
+
+ /* Everything should be the same. */
+ if (!file_id_equal(&state->id, &f->id)) {
+ /* This should catch all dynamic share cases. */
+ state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ break;
+ }
+ if (!strequal(f->servicepath, state->servicepath)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ if (!strequal(f->base_name, state->fname->base_name)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ if (!strequal(f->stream_name, state->fname->stream_name)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+
+ if (NT_STATUS_IS_OK(state->match_status)) {
+ /*
+ * Common case - just opening another handle on a
+ * file on a non-dynamic share.
+ */
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(state->match_status, NT_STATUS_INVALID_PARAMETER)) {
+ /* Mismatched path. Error back to client. */
return;
}
/*
- * More than one file id, or not equal, or new file
- * being created and there's already an existing lease
- * on this (client_guid, lease id) pair.
+ * File id mismatch. Dynamic share case NT_STATUS_OPLOCK_NOT_GRANTED.
* Don't allow leases.
*/
- state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
- state->num_file_ids = num_file_ids;
- state->ids = talloc_memdup(talloc_tos(),
- ids,
- num_file_ids * sizeof(struct file_id));
- if (state->ids == NULL) {
- state->match_status = NT_STATUS_NO_MEMORY;
+ state->match_status = leases_db_copy_file_ids(state->mem_ctx,
+ num_files,
+ files,
+ &state->ids);
+ if (!NT_STATUS_IS_OK(state->match_status)) {
+ return;
}
+
+ state->num_file_ids = num_files;
+ state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ return;
}
static NTSTATUS lease_match(connection_struct *conn,
struct smb_request *req,
struct smb2_lease_key *lease_key,
+ const char *servicepath,
const struct smb_filename *fname,
uint16_t *p_version,
uint16_t *p_epoch)
{
struct smbd_server_connection *sconn = req->sconn;
- struct lease_fname_match_state state = {
+ TALLOC_CTX *tos = talloc_tos();
+ struct lease_match_state state = {
+ .mem_ctx = tos,
+ .servicepath = servicepath,
.fname = fname,
.match_status = NT_STATUS_OK
};
}
status = leases_db_parse(&sconn->client->connections->smb2.client.guid,
- lease_key, lease_fname_match_parser, &state);
+ lease_key, lease_match_parser, &state);
if (!NT_STATUS_IS_OK(status)) {
/*
* Not found or error means okay: We can make the lease pass
continue;
}
- send_break_message(conn->sconn->msg_ctx, e,
+ send_break_message(conn->sconn->msg_ctx, &d->id, e,
SMB2_LEASE_NONE);
/*
status = lease_match(conn,
req,
&lease->lease_key,
+ conn->connectpath,
smb_fname,
&version,
&epoch);
* We can't open a file with DELETE access if any of the
* streams is open without FILE_SHARE_DELETE
*/
- status = open_streams_for_delete(conn, smb_fname->base_name);
+ status = open_streams_for_delete(conn, smb_fname);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
&& is_ntfs_stream_smb_fname(smb_fname)
&& (!(private_flags & NTCREATEX_OPTIONS_PRIVATE_STREAM_DELETE))) {
- uint32 base_create_disposition;
+ uint32_t base_create_disposition;
struct smb_filename *smb_fname_base = NULL;
if (create_options & FILE_DIRECTORY_FILE) {
/* Create an smb_filename with stream_name == NULL. */
smb_fname_base = synthetic_smb_fname(talloc_tos(),
- smb_fname->base_name,
- NULL, NULL);
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->flags);
if (smb_fname_base == NULL) {
status = NT_STATUS_NO_MEMORY;
goto fail;
/* Save the requested allocation size. */
if ((info == FILE_WAS_CREATED) || (info == FILE_WAS_OVERWRITTEN)) {
- if (allocation_size
- && (allocation_size > fsp->fsp_name->st.st_ex_size)) {
+ if ((allocation_size > fsp->fsp_name->st.st_ex_size)
+ && !(fsp->is_directory))
+ {
fsp->initial_allocation_size = smb_roundup(
fsp->conn, allocation_size);
- if (fsp->is_directory) {
- /* Can't set allocation size on a directory. */
- status = NT_STATUS_ACCESS_DENIED;
- goto fail;
- }
if (vfs_allocate_file_space(
fsp, fsp->initial_allocation_size) == -1) {
status = NT_STATUS_DISK_FULL;
files_struct *dir_fsp;
char *parent_fname = NULL;
char *new_base_name = NULL;
+ 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,
- 0,
+ ucf_flags,
NULL,
smb_fname_out);
if (!NT_STATUS_IS_OK(status)) {
struct security_descriptor *sd,
struct ea_list *ea_list,
files_struct **result,
- int *pinfo)
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
{
int info = FILE_WAS_OPENED;
files_struct *fsp = NULL;
status = NT_STATUS_NOT_A_DIRECTORY;
goto fail;
}
- if (lp_posix_pathnames()) {
+ if (req != NULL && req->posix_pathnames) {
ret = SMB_VFS_LSTAT(conn, smb_fname);
} else {
ret = SMB_VFS_STAT(conn, smb_fname);