*/
#include "includes.h"
+#include "system/filesys.h"
+#include "fake_file.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+
+uint32_t ucf_flags_from_smb_request(struct smb_request *req)
+{
+ uint32_t ucf_flags = 0;
+
+ if (req != NULL) {
+ if (req->posix_pathnames) {
+ ucf_flags |= UCF_POSIX_PATHNAMES;
+ }
+ if (req->flags2 & FLAGS2_DFS_PATHNAMES) {
+ ucf_flags |= UCF_DFS_PATHNAME;
+ }
+ if (req->flags2 & FLAGS2_REPARSE_PATH) {
+ ucf_flags |= UCF_GMT_PATHNAME;
+ }
+ }
+
+ return ucf_flags;
+}
+
+uint32_t filename_create_ucf_flags(struct smb_request *req, uint32_t create_disposition)
+{
+ uint32_t ucf_flags = 0;
+
+ ucf_flags |= ucf_flags_from_smb_request(req);
+
+ switch (create_disposition) {
+ case FILE_OPEN:
+ case FILE_OVERWRITE:
+ break;
+ case FILE_SUPERSEDE:
+ case FILE_CREATE:
+ case FILE_OPEN_IF:
+ case FILE_OVERWRITE_IF:
+ ucf_flags |= UCF_PREP_CREATEFILE;
+ break;
+ }
+
+ return ucf_flags;
+}
static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
connection_struct *conn,
- const char *orig_path,
struct smb_filename *smb_fname);
/****************************************************************************
****************************************************************************/
static NTSTATUS determine_path_error(const char *name,
- bool allow_wcard_last_component)
+ bool allow_wcard_last_component,
+ bool posix_pathnames)
{
const char *p;
+ bool name_has_wild = false;
if (!allow_wcard_last_component) {
/* Error code within a pathname. */
p = strchr(name, '/');
- if (!p && (ms_has_wild(name) || ISDOT(name))) {
+ if (!posix_pathnames) {
+ name_has_wild = ms_has_wild(name);
+ }
+
+ if (!p && (name_has_wild || ISDOT(name))) {
/* Error code at the end of a pathname. */
return NT_STATUS_OBJECT_NAME_INVALID;
} else {
}
}
+static NTSTATUS check_for_dot_component(const struct smb_filename *smb_fname)
+{
+ /* Ensure we catch all names with in "/."
+ this is disallowed under Windows and
+ in POSIX they've already been removed. */
+ const char *p = strstr(smb_fname->base_name, "/."); /*mb safe*/
+ if (p) {
+ if (p[2] == '/') {
+ /* Error code within a pathname. */
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ } else if (p[2] == '\0') {
+ /* Error code at the end of a pathname. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Optimization for common case where the missing part
+ is in the last component and the client already
+ sent the correct case.
+ Returns NT_STATUS_OK to mean continue the tree walk
+ (possibly with modified start pointer).
+ Any other NT_STATUS_XXX error means terminate the path
+ lookup here.
+****************************************************************************/
+
+static NTSTATUS check_parent_exists(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ bool posix_pathnames,
+ const struct smb_filename *smb_fname,
+ char **pp_dirpath,
+ char **pp_start)
+{
+ struct smb_filename parent_fname = {0};
+ const char *last_component = NULL;
+ NTSTATUS status;
+ int ret;
+
+ if (!parent_dirname(ctx, smb_fname->base_name,
+ &parent_fname.base_name,
+ &last_component)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!posix_pathnames) {
+ if (ms_has_wild(parent_fname.base_name)) {
+ goto no_optimization_out;
+ }
+ }
+
+ /*
+ * If there was no parent component in
+ * smb_fname->base_name then don't do this
+ * optimization.
+ */
+ if (smb_fname->base_name == last_component) {
+ goto no_optimization_out;
+ }
+
+ if (posix_pathnames) {
+ ret = SMB_VFS_LSTAT(conn, &parent_fname);
+ } else {
+ ret = SMB_VFS_STAT(conn, &parent_fname);
+ }
+
+ /* If the parent stat failed, just continue
+ with the normal tree walk. */
+
+ if (ret == -1) {
+ goto no_optimization_out;
+ }
+
+ status = check_for_dot_component(&parent_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Parent exists - set "start" to be the
+ * last component to shorten the tree walk. */
+
+ /*
+ * Safe to use discard_const_p
+ * here as last_component points
+ * into our smb_fname->base_name.
+ */
+ *pp_start = discard_const_p(char, last_component);
+
+ /* Update dirpath. */
+ TALLOC_FREE(*pp_dirpath);
+ *pp_dirpath = talloc_strdup(ctx, parent_fname.base_name);
+ if (!*pp_dirpath) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(5,("check_parent_exists: name "
+ "= %s, dirpath = %s, "
+ "start = %s\n",
+ smb_fname->base_name,
+ *pp_dirpath,
+ *pp_start));
+
+ return NT_STATUS_OK;
+
+ no_optimization_out:
+
+ /*
+ * We must still return an *pp_dirpath
+ * initialized to ".", and a *pp_start
+ * pointing at smb_fname->base_name.
+ */
+
+ TALLOC_FREE(parent_fname.base_name);
+
+ *pp_dirpath = talloc_strdup(ctx, ".");
+ if (*pp_dirpath == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ /*
+ * Safe to use discard_const_p
+ * here as by convention smb_fname->base_name
+ * is allocated off ctx.
+ */
+ *pp_start = discard_const_p(char, smb_fname->base_name);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Re-order a known good @GMT-token path.
+ */
+
+static NTSTATUS rearrange_snapshot_path(struct smb_filename *smb_fname,
+ char *startp,
+ char *endp)
+{
+ size_t endlen = 0;
+ size_t gmt_len = endp - startp;
+ char gmt_store[gmt_len + 1];
+ char *parent = NULL;
+ const char *last_component = NULL;
+ char *newstr;
+ bool ret;
+
+ DBG_DEBUG("|%s| -> ", smb_fname->base_name);
+
+ /* Save off the @GMT-token. */
+ memcpy(gmt_store, startp, gmt_len);
+ gmt_store[gmt_len] = '\0';
+
+ if (*endp == '/') {
+ /* Remove any trailing '/' */
+ endp++;
+ }
+
+ if (*endp == '\0') {
+ /*
+ * @GMT-token was at end of path.
+ * Remove any preceeding '/'
+ */
+ if (startp > smb_fname->base_name && startp[-1] == '/') {
+ startp--;
+ }
+ }
+
+ /* Remove @GMT-token from the path. */
+ endlen = strlen(endp);
+ memmove(startp, endp, endlen + 1);
+
+ /* Split the remaining path into components. */
+ ret = parent_dirname(smb_fname,
+ smb_fname->base_name,
+ &parent,
+ &last_component);
+ if (ret == false) {
+ /* Must terminate debug with \n */
+ DBG_DEBUG("NT_STATUS_NO_MEMORY\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ISDOT(parent)) {
+ if (last_component[0] == '\0') {
+ newstr = talloc_strdup(smb_fname,
+ gmt_store);
+ } else {
+ newstr = talloc_asprintf(smb_fname,
+ "%s/%s",
+ gmt_store,
+ last_component);
+ }
+ } else {
+ newstr = talloc_asprintf(smb_fname,
+ "%s/%s/%s",
+ gmt_store,
+ parent,
+ last_component);
+ }
+
+ TALLOC_FREE(parent);
+ TALLOC_FREE(smb_fname->base_name);
+ smb_fname->base_name = newstr;
+
+ DBG_DEBUG("|%s|\n", newstr);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Canonicalize any incoming pathname potentially containining
+ * a @GMT-token into a path that looks like:
+ *
+ * @GMT-YYYY-MM-DD-HH-MM-SS/path/name/components/last_component
+ *
+ * Leaves single path @GMT-token -component alone:
+ *
+ * @GMT-YYYY-MM-DD-HH-MM-SS -> @GMT-YYYY-MM-DD-HH-MM-SS
+ *
+ * Eventually when struct smb_filename is updated and the VFS
+ * ABI is changed this will remove the @GMT-YYYY-MM-DD-HH-MM-SS
+ * and store in the struct smb_filename as a struct timeval field
+ * instead.
+ */
+
+static NTSTATUS canonicalize_snapshot_path(struct smb_filename *smb_fname)
+{
+ char *startp = strchr_m(smb_fname->base_name, '@');
+ char *endp = NULL;
+ struct tm tm;
+
+ if (startp == NULL) {
+ /* No @ */
+ return NT_STATUS_OK;
+ }
+
+ startp = strstr_m(startp, "@GMT-");
+ if (startp == NULL) {
+ /* No @ */
+ return NT_STATUS_OK;
+ }
+
+ if ((startp > smb_fname->base_name) && (startp[-1] != '/')) {
+ /* the GMT-token does not start a path-component */
+ return NT_STATUS_OK;
+ }
+
+ endp = strptime(startp, GMT_FORMAT, &tm);
+ if (endp == NULL) {
+ /* Not a valid timestring. */
+ return NT_STATUS_OK;
+ }
+
+ if ( endp[0] == '\0') {
+ return rearrange_snapshot_path(smb_fname,
+ startp,
+ endp);
+ }
+
+ if (endp[0] != '/') {
+ /*
+ * It is not a complete path component, i.e. the path
+ * component continues after the gmt-token.
+ */
+ return NT_STATUS_OK;
+ }
+
+ return rearrange_snapshot_path(smb_fname,
+ startp,
+ endp);
+}
+
/****************************************************************************
This routine is called to convert names from the dos namespace to unix
namespace. It needs to handle any case conversions, mangling, format changes,
If the UCF_SAVE_LCOMP flag is passed in, then the unmodified last component
of the pathname is set in smb_filename->original_lcomp.
-If UCF_ALLOW_WCARD_LCOMP is passed in, then a MS wildcard was detected and
-should be allowed in the last component of the path only.
+If UCF_ALWAYS_ALLOW_WCARD_LCOMP is passed in, then a MS wildcard was detected
+and should be allowed in the last component of the path only.
If the orig_path was a stream, smb_filename->base_name will point to the base
filename, and smb_filename->stream_name will point to the stream name. If
uint32_t ucf_flags)
{
struct smb_filename *smb_fname = NULL;
- char *start, *end;
+
+ /*
+ * This looks strange. But we need "start" initialized to "" here but
+ * it can't be a const char *, so 'char *start = "";' does not work.
+ */
+ char cnull = '\0';
+ char *start = &cnull;
+
+ char *end;
char *dirpath = NULL;
char *stream = NULL;
bool component_was_mangled = False;
bool name_has_wildcard = False;
- bool posix_pathnames = false;
- bool allow_wcard_last_component = ucf_flags & UCF_ALLOW_WCARD_LCOMP;
+ bool posix_pathnames = (ucf_flags & UCF_POSIX_PATHNAMES);
+ bool allow_wcard_last_component =
+ (ucf_flags & UCF_ALWAYS_ALLOW_WCARD_LCOMP);
bool save_last_component = ucf_flags & UCF_SAVE_LCOMP;
+ bool snapshot_path = (ucf_flags & UCF_GMT_PATHNAME);
NTSTATUS status;
int ret = -1;
goto done;
}
+ smb_fname->flags = posix_pathnames ? SMB_FILENAME_POSIX_PATH : 0;
+
DEBUG(5, ("unix_convert called on file \"%s\"\n", orig_path));
/*
status = NT_STATUS_OBJECT_NAME_INVALID;
} else {
status =determine_path_error(&orig_path[2],
- allow_wcard_last_component);
+ allow_wcard_last_component,
+ posix_pathnames);
}
goto err;
}
goto err;
}
+ /* Canonicalize any @GMT- paths. */
+ if (snapshot_path) {
+ status = canonicalize_snapshot_path(smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+ }
+
/*
* Large directory fix normalization. If we're case sensitive, and
* the case preserving parameters are set to "no", normalize the case of
if (conn->case_sensitive && !conn->case_preserve &&
!conn->short_case_preserve) {
- strnorm(smb_fname->base_name, lp_defaultcase(SNUM(conn)));
+ if (!strnorm(smb_fname->base_name, lp_default_case(SNUM(conn)))) {
+ DEBUG(0, ("strnorm %s failed\n", smb_fname->base_name));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err;
+ }
}
/*
}
}
- posix_pathnames = lp_posix_pathnames();
-
/*
* Strip off the stream, and add it back when we're done with the
* base_name.
*/
*stream = '\0';
stream = tmp;
+
+ if (smb_fname->base_name[0] == '\0') {
+ /*
+ * orig_name was just a stream name.
+ * This is a stream on the root of
+ * the share. Replace base_name with
+ * a "."
+ */
+ smb_fname->base_name =
+ talloc_strdup(smb_fname, ".");
+ if (smb_fname->base_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+ if (SMB_VFS_STAT(conn, smb_fname) != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto err;
+ }
+ /* dirpath must exist. */
+ dirpath = talloc_strdup(ctx,".");
+ if (dirpath == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+ DEBUG(5, ("conversion finished %s -> %s\n",
+ orig_path,
+ smb_fname->base_name));
+ goto done;
+ }
}
}
start = smb_fname->base_name;
/*
- * If we're providing case insentive semantics or
+ * If we're providing case insensitive semantics or
* the underlying filesystem is case insensitive,
* then a case-normalized hit in the stat-cache is
* authoratitive. JRA.
if((!conn->case_sensitive || !(conn->fs_capabilities &
FILE_CASE_SENSITIVE_SEARCH)) &&
- stat_cache_lookup(conn, &smb_fname->base_name, &dirpath, &start,
+ stat_cache_lookup(conn, posix_pathnames, &smb_fname->base_name, &dirpath, &start,
&smb_fname->st)) {
goto done;
}
/*
* Make sure "dirpath" is an allocated string, we use this for
- * building the directories with asprintf and free it.
+ * building the directories with talloc_asprintf and free it.
*/
- if ((dirpath == NULL) && (!(dirpath = talloc_strdup(ctx,"")))) {
+ if ((dirpath == NULL) && (!(dirpath = talloc_strdup(ctx,".")))) {
DEBUG(0, ("talloc_strdup failed\n"));
status = NT_STATUS_NO_MEMORY;
goto err;
}
/*
- * stat the name - if it exists then we can add the stream back (if
- * there was one) and be done!
+ * If we have a wildcard we must walk the path to
+ * find where the error is, even if case sensitive
+ * is true.
*/
- if (posix_pathnames) {
- ret = SMB_VFS_LSTAT(conn, smb_fname);
- } else {
- ret = SMB_VFS_STAT(conn, smb_fname);
+ if (!posix_pathnames) {
+ /* POSIX pathnames have no wildcards. */
+ name_has_wildcard = ms_has_wild(smb_fname->base_name);
+ if (name_has_wildcard && !allow_wcard_last_component) {
+ /* Wildcard not valid anywhere. */
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
}
- if (ret == 0) {
- /* Ensure we catch all names with in "/."
- this is disallowed under Windows. */
- const char *p = strstr(smb_fname->base_name, "/."); /*mb safe*/
- if (p) {
- if (p[2] == '/') {
- /* Error code within a pathname. */
- status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n",
+ smb_fname->base_name, dirpath, start));
+
+ if (!name_has_wildcard) {
+ /*
+ * stat the name - if it exists then we can add the stream back (if
+ * there was one) and be done!
+ */
+
+ if (posix_pathnames) {
+ ret = SMB_VFS_LSTAT(conn, smb_fname);
+ } else {
+ ret = SMB_VFS_STAT(conn, smb_fname);
+ }
+
+ if (ret == 0) {
+ status = check_for_dot_component(smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
goto fail;
- } else if (p[2] == '\0') {
- /* Error code at the end of a pathname. */
- status = NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ /* Add the path (not including the stream) to the cache. */
+ stat_cache_add(orig_path, smb_fname->base_name,
+ conn->case_sensitive);
+ DEBUG(5,("conversion of base_name finished %s -> %s\n",
+ orig_path, smb_fname->base_name));
+ goto done;
+ }
+
+ /* Stat failed - ensure we don't use it. */
+ SET_STAT_INVALID(smb_fname->st);
+
+ if (errno == ENOENT) {
+ /* Optimization when creating a new file - only
+ the last component doesn't exist.
+ NOTE : check_parent_exists() doesn't preserve errno.
+ */
+ int saved_errno = errno;
+ status = check_parent_exists(ctx,
+ conn,
+ posix_pathnames,
+ smb_fname,
+ &dirpath,
+ &start);
+ errno = saved_errno;
+ if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
}
- /* Add the path (not including the stream) to the cache. */
- stat_cache_add(orig_path, smb_fname->base_name,
- conn->case_sensitive);
- DEBUG(5,("conversion of base_name finished %s -> %s\n",
- orig_path, smb_fname->base_name));
- goto done;
- }
- DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n",
- smb_fname->base_name, dirpath, start));
+ /*
+ * A special case - if we don't have any wildcards or mangling chars and are case
+ * sensitive or the underlying filesystem is case insensitive then searching
+ * won't help.
+ */
- /*
- * A special case - if we don't have any mangling chars and are case
- * sensitive or the underlying filesystem is case insentive then searching
- * won't help.
- */
+ if ((conn->case_sensitive || !(conn->fs_capabilities &
+ FILE_CASE_SENSITIVE_SEARCH)) &&
+ !mangle_is_mangled(smb_fname->base_name, conn->params)) {
- if ((conn->case_sensitive || !(conn->fs_capabilities &
- FILE_CASE_SENSITIVE_SEARCH)) &&
- !mangle_is_mangled(smb_fname->base_name, conn->params)) {
- goto done;
+ status = check_for_dot_component(smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /*
+ * The stat failed. Could be ok as it could be
+ * a new file.
+ */
+
+ if (errno == ENOTDIR || errno == ELOOP) {
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto fail;
+ } else if (errno == ENOENT) {
+ /*
+ * Was it a missing last component ?
+ * or a missing intermediate component ?
+ */
+ struct smb_filename parent_fname;
+ const char *last_component = NULL;
+
+ ZERO_STRUCT(parent_fname);
+ if (!parent_dirname(ctx, smb_fname->base_name,
+ &parent_fname.base_name,
+ &last_component)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ if (posix_pathnames) {
+ ret = SMB_VFS_LSTAT(conn, &parent_fname);
+ } else {
+ ret = SMB_VFS_STAT(conn, &parent_fname);
+ }
+ if (ret == -1) {
+ if (errno == ENOTDIR ||
+ errno == ENOENT ||
+ errno == ELOOP) {
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto fail;
+ }
+ }
+
+ /*
+ * Missing last component is ok - new file.
+ * Also deal with permission denied elsewhere.
+ * Just drop out to done.
+ */
+ goto done;
+ }
+ }
+ } else {
+ /*
+ * We have a wildcard in the pathname.
+ *
+ * Optimization for common case where the wildcard
+ * is in the last component and the client already
+ * sent the correct case.
+ * NOTE : check_parent_exists() doesn't preserve errno.
+ */
+ int saved_errno = errno;
+ status = check_parent_exists(ctx,
+ conn,
+ posix_pathnames,
+ smb_fname,
+ &dirpath,
+ &start);
+ errno = saved_errno;
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
}
/*
status = NT_STATUS_OBJECT_NAME_INVALID;
} else {
status = determine_path_error(end+1,
- allow_wcard_last_component);
+ allow_wcard_last_component,
+ posix_pathnames);
}
goto fail;
}
/* The name cannot have a wildcard if it's not
the last component. */
- name_has_wildcard = ms_has_wild(start);
-
- /* Wildcard not valid anywhere. */
- if (name_has_wildcard && !allow_wcard_last_component) {
- status = NT_STATUS_OBJECT_NAME_INVALID;
- goto fail;
+ if (!posix_pathnames) {
+ name_has_wildcard = ms_has_wild(start);
}
/* Wildcards never valid within a pathname. */
goto fail;
}
+ /* Skip the stat call if it's a wildcard end. */
+ if (name_has_wildcard) {
+ DEBUG(5,("Wildcard %s\n",start));
+ goto done;
+ }
+
/*
* Check if the name exists up to this point.
*/
/*
* ENOENT/EACCESS are the only valid errors
- * here. EACCESS needs handling here for
- * "dropboxes", i.e. directories where users
- * can only put stuff with permission -wx.
+ * here.
*/
- if ((errno != 0) && (errno != ENOENT)
- && (errno != EACCES)) {
+
+ if (errno == EACCES) {
+ if ((ucf_flags & UCF_PREP_CREATEFILE) == 0) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto fail;
+ } else {
+ /*
+ * This is the dropbox
+ * behaviour. A dropbox is a
+ * directory with only -wx
+ * permissions, so
+ * get_real_filename fails
+ * with EACCESS, it needs to
+ * list the directory. We
+ * nevertheless want to allow
+ * users creating a file.
+ */
+ errno = 0;
+ }
+ }
+
+ if ((errno != 0) && (errno != ENOENT)) {
/*
* ENOTDIR and ELOOP both map to
* NT_STATUS_OBJECT_PATH_NOT_FOUND
(mangle_is_8_3(start, False,
conn->params) &&
!conn->short_case_preserve)) {
- strnorm(start,
- lp_defaultcase(SNUM(conn)));
+ if (!strnorm(start,
+ lp_default_case(SNUM(conn)))) {
+ DEBUG(0, ("strnorm %s failed\n",
+ start));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err;
+ }
}
/*
size_t start_ofs =
start - smb_fname->base_name;
- if (*dirpath != '\0') {
+ if (!ISDOT(dirpath)) {
tmp = talloc_asprintf(
smb_fname, "%s/%s",
dirpath, unmangled);
size_t start_ofs =
start - smb_fname->base_name;
- if (*dirpath != '\0') {
+ if (!ISDOT(dirpath)) {
tmp = talloc_asprintf(smb_fname,
"%s/%s/%s", dirpath,
found_name, end+1);
size_t start_ofs =
start - smb_fname->base_name;
- if (*dirpath != '\0') {
+ if (!ISDOT(dirpath)) {
tmp = talloc_asprintf(smb_fname,
"%s/%s", dirpath,
found_name);
TALLOC_FREE(found_name);
} /* end else */
-#ifdef DEVELOPER
- /*
- * This sucks!
- * We should never provide different behaviors
- * depending on DEVELOPER!!!
- */
- if (VALID_STAT(smb_fname->st)) {
- bool delete_pending;
- get_file_infos(vfs_file_id_from_sbuf(conn,
- &smb_fname->st),
- &delete_pending, NULL);
- if (delete_pending) {
- status = NT_STATUS_DELETE_PENDING;
- goto fail;
- }
- }
-#endif
-
/*
* Add to the dirpath that we have resolved so far.
*/
- if (*dirpath != '\0') {
+ if (!ISDOT(dirpath)) {
char *tmp = talloc_asprintf(ctx,
"%s/%s", dirpath, start);
if (!tmp) {
smb_fname->stream_name = stream;
/* Check path now that the base_name has been converted. */
- status = build_stream_path(ctx, conn, orig_path, smb_fname);
+ status = build_stream_path(ctx, conn, smb_fname);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
return NT_STATUS_OK;
fail:
DEBUG(10, ("dirpath = [%s] start = [%s]\n", dirpath, start));
- if (*dirpath != '\0') {
+ if (dirpath && !ISDOT(dirpath)) {
smb_fname->base_name = talloc_asprintf(smb_fname, "%s/%s",
dirpath, start);
} else {
}
/****************************************************************************
- Check a filename - possibly calling check_reduced_name.
- This is called by every routine before it allows an operation on a filename.
- It does any final confirmation necessary to ensure that the filename is
- a valid one for the user to access.
+ Ensure a path is not vetoed.
****************************************************************************/
-NTSTATUS check_name(connection_struct *conn, const char *name)
+static NTSTATUS check_veto_path(connection_struct *conn,
+ const struct smb_filename *smb_fname)
{
+ const char *name = smb_fname->base_name;
+
if (IS_VETO_PATH(conn, name)) {
/* Is it not dot or dot dot. */
- if (!((name[0] == '.') && (!name[1] ||
- (name[1] == '.' && !name[2])))) {
- DEBUG(5,("check_name: file path name %s vetoed\n",
+ if (!(ISDOT(name) || ISDOTDOT(name))) {
+ DEBUG(5,("check_veto_path: file path name %s vetoed\n",
name));
return map_nt_error_from_unix(ENOENT);
}
}
+ return NT_STATUS_OK;
+}
- if (!lp_widelinks(SNUM(conn)) || !lp_symlinks(SNUM(conn))) {
- NTSTATUS status = check_reduced_name(conn,name);
+/****************************************************************************
+ Check a filename - possibly calling check_reduced_name.
+ This is called by every routine before it allows an operation on a filename.
+ It does any final confirmation necessary to ensure that the filename is
+ a valid one for the user to access.
+****************************************************************************/
+
+NTSTATUS check_name(connection_struct *conn,
+ const struct smb_filename *smb_fname)
+{
+ NTSTATUS status = check_veto_path(conn, smb_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!lp_widelinks(SNUM(conn)) || !lp_follow_symlinks(SNUM(conn))) {
+ status = check_reduced_name(conn, NULL, smb_fname);
if (!NT_STATUS_IS_OK(status)) {
- DEBUG(5,("check_name: name %s failed with %s\n",name,
- nt_errstr(status)));
+ DEBUG(5,("check_name: name %s failed with %s\n",
+ smb_fname->base_name,
+ nt_errstr(status)));
return status;
}
}
return NT_STATUS_OK;
}
+/****************************************************************************
+ Must be called as root. Creates the struct privilege_paths
+ attached to the struct smb_request if this call is successful.
+****************************************************************************/
+
+static NTSTATUS check_name_with_privilege(connection_struct *conn,
+ struct smb_request *smbreq,
+ const struct smb_filename *smb_fname)
+{
+ NTSTATUS status = check_veto_path(conn, smb_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return check_reduced_name_with_privilege(conn,
+ smb_fname,
+ smbreq);
+}
+
/****************************************************************************
Check if two filenames are equal.
This needs to be careful about whether we are case sensitive.
TALLOC_CTX *mem_ctx, char **found_name)
{
struct smb_Dir *cur_dir;
- const char *dname;
+ const char *dname = NULL;
+ char *talloced = NULL;
char *unmangled_name = NULL;
long curpos;
+ struct smb_filename *smb_fname = NULL;
/* handle null paths */
if ((path == NULL) || (*path == 0)) {
}
}
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ path,
+ NULL,
+ NULL,
+ 0);
+ if (smb_fname == NULL) {
+ TALLOC_FREE(unmangled_name);
+ return -1;
+ }
+
/* open the directory */
- if (!(cur_dir = OpenDir(talloc_tos(), conn, path, NULL, 0))) {
+ if (!(cur_dir = OpenDir(talloc_tos(), conn, smb_fname, NULL, 0))) {
DEBUG(3,("scan dir didn't open dir [%s]\n",path));
TALLOC_FREE(unmangled_name);
+ TALLOC_FREE(smb_fname);
return -1;
}
+ TALLOC_FREE(smb_fname);
+
/* now scan for matching names */
curpos = 0;
- while ((dname = ReadDirName(cur_dir, &curpos, NULL))) {
+ while ((dname = ReadDirName(cur_dir, &curpos, NULL, &talloced))) {
/* Is it dot or dot dot. */
if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
continue;
}
TALLOC_FREE(cur_dir);
if (!*found_name) {
errno = ENOMEM;
+ TALLOC_FREE(talloced);
return -1;
}
+ TALLOC_FREE(talloced);
return 0;
}
+ TALLOC_FREE(talloced);
}
TALLOC_FREE(unmangled_name);
int ret;
bool mangled;
+ /* handle null paths */
+ if ((path == NULL) || (*path == 0)) {
+ path = ".";
+ }
+
mangled = mangle_is_mangled(name, conn->params);
if (mangled) {
static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
connection_struct *conn,
- const char *orig_path,
struct smb_filename *smb_fname)
{
NTSTATUS status;
- unsigned int i, num_streams;
+ unsigned int i, num_streams = 0;
struct stream_struct *streams = NULL;
if (SMB_VFS_STAT(conn, smb_fname) == 0) {
}
if (errno != ENOENT) {
+ DEBUG(10, ("vfs_stat failed: %s\n", strerror(errno)));
status = map_nt_error_from_unix(errno);
- DEBUG(10, ("vfs_stat failed: %s\n", nt_errstr(status)));
goto fail;
}
/* Fall back to a case-insensitive scan of all streams on the file. */
- status = SMB_VFS_STREAMINFO(conn, NULL, smb_fname->base_name, mem_ctx,
- &num_streams, &streams);
+ status = vfs_streaminfo(conn, NULL, smb_fname, mem_ctx,
+ &num_streams, &streams);
if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
SET_STAT_INVALID(smb_fname->st);
return status;
}
-/****************************************************************************
- Go through all the steps to validate a filename.
-****************************************************************************/
-
-NTSTATUS filename_convert(TALLOC_CTX *ctx,
+/**
+ * Go through all the steps to validate a filename.
+ *
+ * @param ctx talloc_ctx to allocate memory with.
+ * @param conn connection struct for vfs calls.
+ * @param smbreq SMB request if we're using privileges.
+ * @param name_in The unconverted name.
+ * @param ucf_flags flags to pass through to unix_convert().
+ * UCF_ALWAYS_ALLOW_WCARD_LCOMP will be OR'd in if
+ * p_cont_wcard != NULL and is true and
+ * UCF_COND_ALLOW_WCARD_LCOMP.
+ * @param p_cont_wcard If not NULL, will be set to true if the dfs path
+ * resolution detects a wildcard.
+ * @param pp_smb_fname The final converted name will be allocated if the
+ * return is NT_STATUS_OK.
+ *
+ * @return NT_STATUS_OK if all operations completed successfully, appropriate
+ * error otherwise.
+ */
+static NTSTATUS filename_convert_internal(TALLOC_CTX *ctx,
connection_struct *conn,
- bool dfs_path,
+ struct smb_request *smbreq,
const char *name_in,
+ uint32_t ucf_flags,
+ bool *ppath_contains_wcard,
struct smb_filename **pp_smb_fname)
{
NTSTATUS status;
- char *fname = NULL;
*pp_smb_fname = NULL;
- status = resolve_dfspath(ctx, conn,
- dfs_path,
+ if (ucf_flags & UCF_DFS_PATHNAME) {
+ bool path_contains_wcard = false;
+ char *fname = NULL;
+ status = resolve_dfspath_wcard(ctx, conn,
name_in,
- &fname);
+ ucf_flags,
+ !conn->sconn->using_smb2,
+ &fname,
+ &path_contains_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("filename_convert_internal: resolve_dfspath "
+ "failed for name %s with %s\n",
+ name_in,
+ nt_errstr(status) ));
+ return status;
+ }
+ name_in = fname;
+ if (ppath_contains_wcard != NULL && path_contains_wcard) {
+ *ppath_contains_wcard = path_contains_wcard;
+ }
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+ }
+
+ if (is_fake_file_path(name_in)) {
+ SMB_STRUCT_STAT st;
+ ZERO_STRUCT(st);
+ st.st_ex_nlink = 1;
+ *pp_smb_fname = synthetic_smb_fname_split(ctx,
+ name_in,
+ (ucf_flags & UCF_POSIX_PATHNAMES));
+ if (*pp_smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ (*pp_smb_fname)->st = st;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * If the caller conditionally allows wildcard lookups, only add the
+ * always allow if the path actually does contain a wildcard.
+ */
+ if (ucf_flags & UCF_COND_ALLOW_WCARD_LCOMP &&
+ ppath_contains_wcard != NULL && *ppath_contains_wcard) {
+ ucf_flags |= UCF_ALWAYS_ALLOW_WCARD_LCOMP;
+ }
+
+ status = unix_convert(ctx, conn, name_in, pp_smb_fname, ucf_flags);
if (!NT_STATUS_IS_OK(status)) {
- DEBUG(10,("filename_convert: resolve_dfspath failed "
+ DEBUG(10,("filename_convert_internal: unix_convert failed "
"for name %s with %s\n",
name_in,
nt_errstr(status) ));
return status;
}
- status = unix_convert(ctx, conn, fname, pp_smb_fname, 0);
- if (!NT_STATUS_IS_OK(status)) {
- DEBUG(10,("filename_convert: unix_convert failed "
- "for name %s with %s\n",
- fname,
- nt_errstr(status) ));
- return status;
+
+ if ((ucf_flags & UCF_UNIX_NAME_LOOKUP) &&
+ VALID_STAT((*pp_smb_fname)->st) &&
+ S_ISLNK((*pp_smb_fname)->st.st_ex_mode)) {
+ return check_veto_path(conn, (*pp_smb_fname));
}
- status = check_name(conn, (*pp_smb_fname)->base_name);
+ if (!smbreq) {
+ status = check_name(conn, (*pp_smb_fname));
+ } else {
+ status = check_name_with_privilege(conn, smbreq,
+ (*pp_smb_fname));
+ }
if (!NT_STATUS_IS_OK(status)) {
- DEBUG(3,("filename_convert: check_name failed "
+ DEBUG(3,("filename_convert_internal: check_name failed "
"for name %s with %s\n",
smb_fname_str_dbg(*pp_smb_fname),
nt_errstr(status) ));
return status;
}
+
+/*
+ * Go through all the steps to validate a filename.
+ * Non-root version.
+ */
+
+NTSTATUS filename_convert(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *name_in,
+ uint32_t ucf_flags,
+ bool *ppath_contains_wcard,
+ struct smb_filename **pp_smb_fname)
+{
+ return filename_convert_internal(ctx,
+ conn,
+ NULL,
+ name_in,
+ ucf_flags,
+ ppath_contains_wcard,
+ pp_smb_fname);
+}
+
+/*
+ * Go through all the steps to validate a filename.
+ * root (privileged) version.
+ */
+
+NTSTATUS filename_convert_with_privilege(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *smbreq,
+ const char *name_in,
+ uint32_t ucf_flags,
+ bool *ppath_contains_wcard,
+ struct smb_filename **pp_smb_fname)
+{
+ return filename_convert_internal(ctx,
+ conn,
+ smbreq,
+ name_in,
+ ucf_flags,
+ ppath_contains_wcard,
+ pp_smb_fname);
+}