#include "smbd/globals.h"
#include "lib/util/memcache.h"
-static NTSTATUS get_real_filename(connection_struct *conn,
- struct smb_filename *path,
- const char *name,
- TALLOC_CTX *mem_ctx,
- char **found_name);
-
-static NTSTATUS check_name(connection_struct *conn,
- const struct smb_filename *smb_fname);
-
uint32_t ucf_flags_from_smb_request(struct smb_request *req)
{
uint32_t ucf_flags = 0;
return ucf_flags;
}
-static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
- connection_struct *conn,
- struct smb_filename *smb_fname);
-
/****************************************************************************
Mangle the 2nd name and check if it is then equal to the first name.
****************************************************************************/
return strequal(name1, mname);
}
-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,
- int *p_parent_stat_errno)
-{
- char *parent_name = NULL;
- struct smb_filename *parent_fname = NULL;
- const char *last_component = NULL;
- NTSTATUS status;
- int ret;
-
- if (!parent_dirname(ctx, smb_fname->base_name,
- &parent_name,
- &last_component)) {
- return NT_STATUS_NO_MEMORY;
- }
-
- if (!posix_pathnames) {
- if (ms_has_wild(parent_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;
- }
-
- parent_fname = synthetic_smb_fname(ctx,
- parent_name,
- NULL,
- NULL,
- smb_fname->twrp,
- smb_fname->flags);
- if (parent_fname == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
-
- ret = vfs_stat(conn, parent_fname);
-
- /* If the parent stat failed, just continue
- with the normal tree walk. */
-
- if (ret == -1) {
- /*
- * Optimization. Preserving the
- * errno from the STAT/LSTAT here
- * will allow us to save a duplicate
- * STAT/LSTAT system call of the parent
- * pathname in a hot code path in the caller.
- */
- if (p_parent_stat_errno != NULL) {
- *p_parent_stat_errno = errno;
- }
- 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_name);
- TALLOC_FREE(parent_fname);
-
- *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;
-}
-
static bool find_snapshot_token(
const char *filename,
const char **_start,
{
const char *start = NULL;
const char *end = NULL;
- struct tm tm;
- time_t t;
-
- start = strstr_m(filename, "@GMT-");
-
- if (start == NULL) {
- return false;
- }
-
- if ((start > filename) && (start[-1] != '/')) {
- /* the GMT-token does not start a path-component */
- return false;
- }
-
- end = strptime(start, GMT_FORMAT, &tm);
- if (end == NULL) {
- /* Not a valid timestring. */
- return false;
- }
-
- if ((end[0] != '\0') && (end[0] != '/')) {
- /*
- * It is not a complete path component, i.e. the path
- * component continues after the gmt-token.
- */
- return false;
- }
-
- tm.tm_isdst = -1;
- t = timegm(&tm);
- unix_to_nt_time(twrp, t);
-
- DBG_DEBUG("Extracted @GMT-Timestamp %s\n",
- nt_time_string(talloc_tos(), *twrp));
-
- *_start = start;
-
- if (end[0] == '/') {
- end += 1;
- }
- *_next_component = end;
-
- return true;
-}
-
-bool extract_snapshot_token(char *fname, NTTIME *twrp)
-{
- const char *start = NULL;
- const char *next = NULL;
- size_t remaining;
- bool found;
-
- found = find_snapshot_token(fname, &start, &next, twrp);
- if (!found) {
- return false;
- }
-
- remaining = strlen(next);
- memmove(discard_const_p(char, start), next, remaining+1);
-
- return true;
-}
-
-/*
- * Strip a valid @GMT-token from any incoming filename path,
- * adding any NTTIME encoded in the pathname into the
- * twrp field of the passed in smb_fname.
- *
- * Valid @GMT-tokens look like @GMT-YYYY-MM-DD-HH-MM-SS
- * at the *start* of a pathname component.
- *
- * If twrp is passed in then smb_fname->twrp is set to that
- * value, and the @GMT-token part of the filename is removed
- * and does not change the stored smb_fname->twrp.
- *
- */
-
-NTSTATUS canonicalize_snapshot_path(struct smb_filename *smb_fname,
- uint32_t ucf_flags,
- NTTIME twrp)
-{
- bool found;
-
- if (twrp != 0) {
- smb_fname->twrp = twrp;
- }
-
- if (!(ucf_flags & UCF_GMT_PATHNAME)) {
- return NT_STATUS_OK;
- }
-
- found = extract_snapshot_token(smb_fname->base_name, &twrp);
- if (!found) {
- return NT_STATUS_OK;
- }
-
- if (smb_fname->twrp == 0) {
- smb_fname->twrp = twrp;
- }
-
- return NT_STATUS_OK;
-}
-
-static bool strnorm(char *s, int case_default)
-{
- if (case_default == CASE_UPPER)
- return strupper_m(s);
- else
- return strlower_m(s);
-}
-
-/*
- * Utility function to normalize case on an incoming client filename
- * if required on this connection struct.
- * Performs an in-place case conversion guaranteed to stay the same size.
- */
-
-static NTSTATUS normalize_filename_case(connection_struct *conn,
- char *filename,
- uint32_t ucf_flags)
-{
- bool ok;
-
- if (ucf_flags & UCF_POSIX_PATHNAMES) {
- /*
- * POSIX never normalizes filename case.
- */
- return NT_STATUS_OK;
- }
- if (!conn->case_sensitive) {
- return NT_STATUS_OK;
- }
- if (conn->case_preserve) {
- return NT_STATUS_OK;
- }
- if (conn->short_case_preserve) {
- return NT_STATUS_OK;
- }
- ok = strnorm(filename, lp_default_case(SNUM(conn)));
- if (!ok) {
- return NT_STATUS_INVALID_PARAMETER;
- }
- return NT_STATUS_OK;
-}
-
-/****************************************************************************
-This routine is called to convert names from the dos namespace to unix
-namespace. It needs to handle any case conversions, mangling, format changes,
-streams etc.
-
-We assume that we have already done a chdir() to the right "root" directory
-for this service.
-
-Conversion to basic unix format is already done in check_path_syntax().
-
-Names must be relative to the root of the service - any leading /. and
-trailing /'s should have been trimmed by check_path_syntax().
-
-The function will return an NTSTATUS error if some part of the name except for
-the last part cannot be resolved, else NT_STATUS_OK.
-
-Note NT_STATUS_OK doesn't mean the name exists or is valid, just that we
-didn't get any fatal errors that should immediately terminate the calling SMB
-processing whilst resolving.
-
-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
-orig_path was not a stream, then smb_filename->stream_name will be NULL.
-
-On exit from unix_convert, the smb_filename->st stat struct will be populated
-if the file exists and was found, if not this stat struct will be filled with
-zeros (and this can be detected by checking for nlinks = 0, which can never be
-true for any file).
-****************************************************************************/
-
-struct uc_state {
- TALLOC_CTX *mem_ctx;
- struct connection_struct *conn;
- struct smb_filename *smb_fname;
- const char *orig_path;
- uint32_t ucf_flags;
- char *name;
- char *end;
- char *dirpath;
- char *stream;
- bool component_was_mangled;
- bool posix_pathnames;
- bool done;
- bool case_sensitive;
- bool case_preserve;
- bool short_case_preserve;
-};
-
-static NTSTATUS unix_convert_step_search_fail(
- struct uc_state *state, NTSTATUS status)
-{
- char *unmangled;
-
- if (state->end) {
- /*
- * An intermediate part of the name
- * can't be found.
- */
- DBG_DEBUG("Intermediate [%s] missing\n",
- state->name);
- *state->end = '/';
-
- /*
- * We need to return the fact that the
- * intermediate name resolution failed.
- * This is used to return an error of
- * ERRbadpath rather than ERRbadfile.
- * Some Windows applications depend on
- * the difference between these two
- * errors.
- */
-
- /*
- * ENOENT, ENOTDIR and ELOOP all map
- * to NT_STATUS_OBJECT_PATH_NOT_FOUND
- * in the filename walk.
- */
- if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) ||
- NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK) ||
- NT_STATUS_EQUAL(status, NT_STATUS_NOT_A_DIRECTORY)) {
- status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
- }
- return status;
- }
-
- /*
- * ENOENT/EACCESS are the only valid errors
- * here.
- */
-
- if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
- if ((state->ucf_flags & UCF_PREP_CREATEFILE) == 0) {
- /*
- * Could be a symlink pointing to
- * a directory outside the share
- * to which we don't have access.
- * If so, we need to know that here
- * so we can return the correct error code.
- * check_name() is never called if we
- * error out of filename_convert().
- */
- int ret;
- struct smb_filename dname = (struct smb_filename) {
- .base_name = state->dirpath,
- .twrp = state->smb_fname->twrp,
- };
-
- /* handle null paths */
- if ((dname.base_name == NULL) ||
- (dname.base_name[0] == '\0')) {
- return NT_STATUS_ACCESS_DENIED;
- }
- ret = SMB_VFS_LSTAT(state->conn, &dname);
- if (ret != 0) {
- return NT_STATUS_ACCESS_DENIED;
- }
- if (!S_ISLNK(dname.st.st_ex_mode)) {
- return NT_STATUS_ACCESS_DENIED;
- }
- status = check_name(state->conn, &dname);
- if (!NT_STATUS_IS_OK(status)) {
- /* We know this is an intermediate path. */
- return NT_STATUS_OBJECT_PATH_NOT_FOUND;
- }
- return NT_STATUS_ACCESS_DENIED;
- } 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.
- */
- status = NT_STATUS_OK;
- }
- }
-
- if (!NT_STATUS_IS_OK(status) &&
- !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
- /*
- * ENOTDIR and ELOOP both map to
- * NT_STATUS_OBJECT_PATH_NOT_FOUND
- * in the filename walk.
- */
- if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_A_DIRECTORY) ||
- NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
- status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
- }
- return status;
- }
-
- /*
- * POSIX pathnames must never call into mangling.
- */
- if (state->posix_pathnames) {
- goto done;
- }
-
- /*
- * Just the last part of the name doesn't exist.
- * We need to strupper() or strlower() it as
- * this conversion may be used for file creation
- * purposes. Fix inspired by
- * Thomas Neumann <t.neumann@iku-ag.de>.
- */
- if (!state->case_preserve ||
- (mangle_is_8_3(state->name, false,
- state->conn->params) &&
- !state->short_case_preserve)) {
- if (!strnorm(state->name,
- lp_default_case(SNUM(state->conn)))) {
- DBG_DEBUG("strnorm %s failed\n",
- state->name);
- return NT_STATUS_INVALID_PARAMETER;
- }
- }
-
- /*
- * check on the mangled stack to see if we can
- * recover the base of the filename.
- */
-
- if (mangle_is_mangled(state->name, state->conn->params)
- && mangle_lookup_name_from_8_3(state->mem_ctx,
- state->name,
- &unmangled,
- state->conn->params)) {
- char *tmp;
- size_t name_ofs =
- state->name - state->smb_fname->base_name;
-
- if (!ISDOT(state->dirpath)) {
- tmp = talloc_asprintf(
- state->smb_fname, "%s/%s",
- state->dirpath, unmangled);
- TALLOC_FREE(unmangled);
- }
- else {
- tmp = unmangled;
- }
- if (tmp == NULL) {
- DBG_ERR("talloc failed\n");
- return NT_STATUS_NO_MEMORY;
- }
- TALLOC_FREE(state->smb_fname->base_name);
- state->smb_fname->base_name = tmp;
- state->name =
- state->smb_fname->base_name + name_ofs;
- state->end = state->name + strlen(state->name);
- }
-
- done:
-
- DBG_DEBUG("New file [%s]\n", state->name);
- state->done = true;
- return NT_STATUS_OK;
-}
-
-static NTSTATUS unix_convert_step_stat(struct uc_state *state)
-{
- struct smb_filename dname;
- char dot[2] = ".";
- char *found_name = NULL;
- int ret;
- NTSTATUS status;
-
- /*
- * Check if the name exists up to this point.
- */
-
- DBG_DEBUG("smb_fname [%s]\n", smb_fname_str_dbg(state->smb_fname));
-
- ret = vfs_stat(state->conn, state->smb_fname);
- if (ret == 0) {
- /*
- * It exists. it must either be a directory or this must
- * be the last part of the path for it to be OK.
- */
- if (state->end && !S_ISDIR(state->smb_fname->st.st_ex_mode)) {
- /*
- * An intermediate part of the name isn't
- * a directory.
- */
- DBG_DEBUG("Not a dir [%s]\n", state->name);
- *state->end = '/';
- /*
- * We need to return the fact that the
- * intermediate name resolution failed. This
- * is used to return an error of ERRbadpath
- * rather than ERRbadfile. Some Windows
- * applications depend on the difference between
- * these two errors.
- */
- return NT_STATUS_OBJECT_PATH_NOT_FOUND;
- }
- return NT_STATUS_OK;
- }
-
- /* Stat failed - ensure we don't use it. */
- SET_STAT_INVALID(state->smb_fname->st);
-
- if (state->posix_pathnames) {
- /*
- * For posix_pathnames, we're done.
- * Don't blunder into the
- * get_real_filename() codepath as they may
- * be doing case insensitive lookups. So when
- * creating a new POSIX directory Foo they might
- * match on name foo.
- *
- * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13803
- */
- if (state->end != NULL) {
- const char *morepath = NULL;
- /*
- * If this is intermediate we must
- * restore the full path.
- */
- *state->end = '/';
- /*
- * If there are any more components
- * after the failed LSTAT we cannot
- * continue.
- */
- morepath = strchr(state->end + 1, '/');
- if (morepath != NULL) {
- return NT_STATUS_OBJECT_PATH_NOT_FOUND;
- }
- }
- if (errno == ENOENT) {
- /* New file or directory. */
- state->done = true;
- return NT_STATUS_OK;
- }
- if ((errno == EACCES) &&
- (state->ucf_flags & UCF_PREP_CREATEFILE)) {
- /* POSIX Dropbox case. */
- errno = 0;
- state->done = true;
- return NT_STATUS_OK;
- }
- return map_nt_error_from_unix(errno);
- }
-
- /*
- * Reset errno so we can detect
- * directory open errors.
- */
- errno = 0;
-
- /*
- * Try to find this part of the path in the directory.
- */
-
- dname = (struct smb_filename) {
- .base_name = state->dirpath,
- .twrp = state->smb_fname->twrp,
- };
-
- /* handle null paths */
- if ((dname.base_name == NULL) || (dname.base_name[0] == '\0')) {
- dname.base_name = dot;
- }
-
- status = get_real_filename(state->conn,
- &dname,
- state->name,
- talloc_tos(),
- &found_name);
- if (!NT_STATUS_IS_OK(status)) {
- return unix_convert_step_search_fail(state, status);
- }
-
- /*
- * Restore the rest of the string. If the string was
- * mangled the size may have changed.
- */
- if (state->end) {
- char *tmp;
- size_t name_ofs =
- state->name - state->smb_fname->base_name;
-
- if (!ISDOT(state->dirpath)) {
- tmp = talloc_asprintf(state->smb_fname,
- "%s/%s/%s", state->dirpath,
- found_name, state->end+1);
- }
- else {
- tmp = talloc_asprintf(state->smb_fname,
- "%s/%s", found_name,
- state->end+1);
- }
- if (tmp == NULL) {
- DBG_ERR("talloc_asprintf failed\n");
- return NT_STATUS_NO_MEMORY;
- }
- TALLOC_FREE(state->smb_fname->base_name);
- state->smb_fname->base_name = tmp;
- state->name = state->smb_fname->base_name + name_ofs;
- state->end = state->name + strlen(found_name);
- *state->end = '\0';
- } else {
- char *tmp;
- size_t name_ofs =
- state->name - state->smb_fname->base_name;
-
- if (!ISDOT(state->dirpath)) {
- tmp = talloc_asprintf(state->smb_fname,
- "%s/%s", state->dirpath,
- found_name);
- } else {
- tmp = talloc_strdup(state->smb_fname,
- found_name);
- }
- if (tmp == NULL) {
- DBG_ERR("talloc failed\n");
- return NT_STATUS_NO_MEMORY;
- }
- TALLOC_FREE(state->smb_fname->base_name);
- state->smb_fname->base_name = tmp;
- state->name = state->smb_fname->base_name + name_ofs;
-
- /*
- * We just scanned for, and found the end of
- * the path. We must return a valid stat struct
- * if it exists. JRA.
- */
-
- ret = vfs_stat(state->conn, state->smb_fname);
- if (ret != 0) {
- SET_STAT_INVALID(state->smb_fname->st);
- }
- }
-
- TALLOC_FREE(found_name);
- return NT_STATUS_OK;
-}
-
-static NTSTATUS unix_convert_step(struct uc_state *state)
-{
- NTSTATUS status;
-
- /*
- * Pinpoint the end of this section of the filename.
- */
- /* mb safe. '/' can't be in any encoded char. */
- state->end = strchr(state->name, '/');
-
- /*
- * Chop the name at this point.
- */
- if (state->end != NULL) {
- *state->end = 0;
- }
-
- DBG_DEBUG("dirpath [%s] name [%s]\n", state->dirpath, state->name);
-
- /* The name cannot have a component of "." */
-
- if (ISDOT(state->name)) {
- if (state->end == NULL) {
- /* Error code at the end of a pathname. */
- return NT_STATUS_OBJECT_NAME_INVALID;
- }
- return NT_STATUS_OBJECT_PATH_NOT_FOUND;
- }
-
- status = unix_convert_step_stat(state);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
- if (state->done) {
- return NT_STATUS_OK;
- }
-
- /*
- * Add to the dirpath that we have resolved so far.
- */
-
- if (!ISDOT(state->dirpath)) {
- char *tmp = talloc_asprintf(state->mem_ctx,
- "%s/%s", state->dirpath, state->name);
- if (!tmp) {
- DBG_ERR("talloc_asprintf failed\n");
- return NT_STATUS_NO_MEMORY;
- }
- TALLOC_FREE(state->dirpath);
- state->dirpath = tmp;
- }
- else {
- TALLOC_FREE(state->dirpath);
- if (!(state->dirpath = talloc_strdup(state->mem_ctx,state->name))) {
- DBG_ERR("talloc_strdup failed\n");
- return NT_STATUS_NO_MEMORY;
- }
- }
-
- /*
- * Cache the dirpath thus far. Don't cache a name with mangled
- * components as this can change the size.
- */
- if(!state->component_was_mangled) {
- stat_cache_add(state->orig_path,
- state->dirpath,
- state->smb_fname->twrp,
- state->case_sensitive);
- }
-
- /*
- * Restore the / that we wiped out earlier.
- */
- if (state->end != NULL) {
- *state->end = '/';
- }
-
- return NT_STATUS_OK;
-}
-
-NTSTATUS unix_convert(TALLOC_CTX *mem_ctx,
- connection_struct *conn,
- const char *orig_path,
- NTTIME twrp,
- struct smb_filename **smb_fname_out,
- uint32_t ucf_flags)
-{
- struct uc_state uc_state;
- struct uc_state *state = &uc_state;
- NTSTATUS status;
- int ret = -1;
- int parent_stat_errno = 0;
-
- *state = (struct uc_state) {
- .mem_ctx = mem_ctx,
- .conn = conn,
- .orig_path = orig_path,
- .ucf_flags = ucf_flags,
- .posix_pathnames = (ucf_flags & UCF_POSIX_PATHNAMES),
- .case_sensitive = conn->case_sensitive,
- .case_preserve = conn->case_preserve,
- .short_case_preserve = conn->short_case_preserve,
- };
-
- *smb_fname_out = NULL;
-
- if (state->posix_pathnames) {
- /* POSIX means ignore case settings on share. */
- state->case_sensitive = true;
- state->case_preserve = true;
- state->short_case_preserve = true;
- }
-
- state->smb_fname = talloc_zero(state->mem_ctx, struct smb_filename);
- if (state->smb_fname == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
-
- if (state->conn->printer) {
- /* we don't ever use the filenames on a printer share as a
- filename - so don't convert them */
- state->smb_fname->base_name = talloc_strdup(
- state->smb_fname, state->orig_path);
- if (state->smb_fname->base_name == NULL) {
- status = NT_STATUS_NO_MEMORY;
- goto err;
- }
- goto done;
- }
-
- state->smb_fname->flags = state->posix_pathnames ? SMB_FILENAME_POSIX_PATH : 0;
-
- DBG_DEBUG("Called on file [%s]\n", state->orig_path);
-
- if (state->orig_path[0] == '/') {
- DBG_ERR("Path [%s] starts with '/'\n", state->orig_path);
- return NT_STATUS_OBJECT_NAME_INVALID;
- }
-
- /* Start with the full orig_path as given by the caller. */
- state->smb_fname->base_name = talloc_strdup(
- state->smb_fname, state->orig_path);
- if (state->smb_fname->base_name == NULL) {
- DBG_ERR("talloc_strdup failed\n");
- status = NT_STATUS_NO_MEMORY;
- goto err;
- }
-
- /* Canonicalize any @GMT- paths. */
- status = canonicalize_snapshot_path(state->smb_fname, ucf_flags, twrp);
- if (!NT_STATUS_IS_OK(status)) {
- goto err;
- }
-
- /*
- * If we trimmed down to a single '\0' character
- * then we should use the "." directory to avoid
- * searching the cache, but not if we are in a
- * printing share.
- * As we know this is valid we can return true here.
- */
-
- if (state->smb_fname->base_name[0] == '\0') {
- state->smb_fname->base_name = talloc_strdup(state->smb_fname, ".");
- if (state->smb_fname->base_name == NULL) {
- status = NT_STATUS_NO_MEMORY;
- goto err;
- }
- if (SMB_VFS_STAT(state->conn, state->smb_fname) != 0) {
- status = map_nt_error_from_unix(errno);
- goto err;
- }
- DBG_DEBUG("conversion finished [] -> [%s]\n",
- state->smb_fname->base_name);
- goto done;
- }
-
- if (state->orig_path[0] == '.' && (state->orig_path[1] == '/' ||
- state->orig_path[1] == '\0')) {
- /* Start of pathname can't be "." only. */
- if (state->orig_path[1] == '\0' || state->orig_path[2] == '\0') {
- status = NT_STATUS_OBJECT_NAME_INVALID;
- } else {
- status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
- }
- goto err;
- }
-
- /*
- * Large directory fix normalization. If we're case sensitive, and
- * the case preserving parameters are set to "no", normalize the case of
- * the incoming filename from the client WHETHER IT EXISTS OR NOT !
- * This is in conflict with the current (3.0.20) man page, but is
- * what people expect from the "large directory howto". I'll update
- * the man page. Thanks to jht@samba.org for finding this. JRA.
- */
-
- status = normalize_filename_case(state->conn,
- state->smb_fname->base_name,
- ucf_flags);
- if (!NT_STATUS_IS_OK(status)) {
- DBG_ERR("normalize_filename_case %s failed\n",
- state->smb_fname->base_name);
- goto err;
- }
-
- /*
- * Strip off the stream, and add it back when we're done with the
- * base_name.
- */
- if (!state->posix_pathnames) {
- state->stream = strchr_m(state->smb_fname->base_name, ':');
-
- if (state->stream != NULL) {
- char *tmp = talloc_strdup(state->smb_fname, state->stream);
- if (tmp == NULL) {
- status = NT_STATUS_NO_MEMORY;
- goto err;
- }
- /*
- * Since this is actually pointing into
- * smb_fname->base_name this truncates base_name.
- */
- *state->stream = '\0';
- state->stream = tmp;
-
- if (state->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 "."
- */
- state->smb_fname->base_name =
- talloc_strdup(state->smb_fname, ".");
- if (state->smb_fname->base_name == NULL) {
- status = NT_STATUS_NO_MEMORY;
- goto err;
- }
- if (SMB_VFS_STAT(state->conn, state->smb_fname) != 0) {
- status = map_nt_error_from_unix(errno);
- goto err;
- }
- /* dirpath must exist. */
- state->dirpath = talloc_strdup(state->mem_ctx,".");
- if (state->dirpath == NULL) {
- status = NT_STATUS_NO_MEMORY;
- goto err;
- }
- DBG_INFO("conversion finished [%s] -> [%s]\n",
- state->orig_path,
- state->smb_fname->base_name);
- goto done;
- }
- }
- }
-
- state->name = state->smb_fname->base_name;
-
- /*
- * If we're providing case insensitive semantics or
- * the underlying filesystem is case insensitive,
- * then a case-normalized hit in the stat-cache is
- * authoritative. JRA.
- *
- * Note: We're only checking base_name. The stream_name will be
- * added and verified in build_stream_path().
- */
-
- if (!state->case_sensitive ||
- !(state->conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH))
- {
- bool found;
-
- found = stat_cache_lookup(state->conn,
- &state->smb_fname->base_name,
- &state->dirpath,
- &state->name,
- state->smb_fname->twrp,
- &state->smb_fname->st);
- /*
- * stat_cache_lookup() allocates on talloc_tos() even
- * when !found, reparent correctly
- */
- talloc_steal(state->smb_fname, state->smb_fname->base_name);
- talloc_steal(state->mem_ctx, state->dirpath);
-
- if (found) {
- goto done;
- }
- }
-
- /*
- * Make sure "dirpath" is an allocated string, we use this for
- * building the directories with talloc_asprintf and free it.
- */
-
- if (state->dirpath == NULL) {
- state->dirpath = talloc_strdup(state->mem_ctx,".");
- if (state->dirpath == NULL) {
- DBG_ERR("talloc_strdup failed\n");
- status = NT_STATUS_NO_MEMORY;
- goto err;
- }
- }
-
- /*
- * If we have a wildcard we must walk the path to
- * find where the error is, even if case sensitive
- * is true.
- */
-
- if (!state->posix_pathnames) {
- /* POSIX pathnames have no wildcards. */
- bool name_has_wildcard = ms_has_wild(state->smb_fname->base_name);
- if (name_has_wildcard) {
- /* Wildcard not valid anywhere. */
- status = NT_STATUS_OBJECT_NAME_INVALID;
- goto fail;
- }
- }
-
- DBG_DEBUG("Begin: name [%s] dirpath [%s] name [%s]\n",
- state->smb_fname->base_name, state->dirpath, state->name);
-
- /*
- * stat the name - if it exists then we can add the stream back (if
- * there was one) and be done!
- */
-
- ret = vfs_stat(state->conn, state->smb_fname);
- if (ret == 0) {
- status = check_for_dot_component(state->smb_fname);
- if (!NT_STATUS_IS_OK(status)) {
- goto fail;
- }
- /* Add the path (not including the stream) to the cache. */
- stat_cache_add(state->orig_path,
- state->smb_fname->base_name,
- state->smb_fname->twrp,
- state->case_sensitive);
- DBG_DEBUG("Conversion of base_name finished "
- "[%s] -> [%s]\n",
- state->orig_path, state->smb_fname->base_name);
- goto done;
- }
-
- /* Stat failed - ensure we don't use it. */
- SET_STAT_INVALID(state->smb_fname->st);
-
- /*
- * Note: we must continue processing a path if we get EACCES
- * from stat. With NFS4 permissions the file might be lacking
- * READ_ATTR, but if the parent has LIST permissions we can
- * resolve the path in the path traversal loop down below.
- */
-
- 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(state->mem_ctx,
- state->conn,
- state->posix_pathnames,
- state->smb_fname,
- &state->dirpath,
- &state->name,
- &parent_stat_errno);
- errno = saved_errno;
- if (!NT_STATUS_IS_OK(status)) {
- goto fail;
- }
- }
-
- /*
- * 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.
- *
- * NB. As POSIX sets state->case_sensitive as
- * true we will never call into mangle_is_mangled() here.
- */
-
- if ((state->case_sensitive || !(state->conn->fs_capabilities &
- FILE_CASE_SENSITIVE_SEARCH)) &&
- !mangle_is_mangled(state->smb_fname->base_name, state->conn->params)) {
-
- status = check_for_dot_component(state->smb_fname);
- if (!NT_STATUS_IS_OK(status)) {
- goto fail;
- }
+ struct tm tm;
+ time_t t;
- /*
- * The stat failed. Could be ok as it could be
- * a new file.
- */
+ start = strstr_m(filename, "@GMT-");
- 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 ?
- *
- * Optimization.
- *
- * For this code path we can guarantee that
- * we have gone through check_parent_exists()
- * and it returned NT_STATUS_OK.
- *
- * Either there was no parent component (".")
- * parent_stat_errno == 0 and we have a missing
- * last component here.
- *
- * OR check_parent_exists() called STAT/LSTAT
- * and if it failed parent_stat_errno has been
- * set telling us if the parent existed or not.
- *
- * Either way we can avoid another STAT/LSTAT
- * system call on the parent here.
- */
- if (parent_stat_errno == ENOTDIR ||
- parent_stat_errno == ENOENT ||
- parent_stat_errno == ELOOP) {
- status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
- goto fail;
- }
+ if (start == NULL) {
+ return false;
+ }
- /*
- * Missing last component is ok - new file.
- * Also deal with permission denied elsewhere.
- * Just drop out to done.
- */
- goto done;
- }
+ if ((start > filename) && (start[-1] != '/')) {
+ /* the GMT-token does not start a path-component */
+ return false;
}
- /*
- * is_mangled() was changed to look at an entire pathname, not
- * just a component. JRA.
- */
+ end = strptime(start, GMT_FORMAT, &tm);
+ if (end == NULL) {
+ /* Not a valid timestring. */
+ return false;
+ }
- if (state->posix_pathnames) {
+ if ((end[0] != '\0') && (end[0] != '/')) {
/*
- * POSIX names are never mangled and we must not
- * call into mangling functions.
+ * It is not a complete path component, i.e. the path
+ * component continues after the gmt-token.
*/
- state->component_was_mangled = false;
- } else if (mangle_is_mangled(state->name, state->conn->params)) {
- state->component_was_mangled = true;
+ return false;
}
- /*
- * Now we need to recursively match the name against the real
- * directory structure.
- */
+ tm.tm_isdst = -1;
+ t = timegm(&tm);
+ unix_to_nt_time(twrp, t);
- /*
- * Match each part of the path name separately, trying the names
- * as is first, then trying to scan the directory for matching names.
- */
+ DBG_DEBUG("Extracted @GMT-Timestamp %s\n",
+ nt_time_string(talloc_tos(), *twrp));
- for (; state->name ; state->name = (state->end ? state->end + 1:(char *)NULL)) {
- status = unix_convert_step(state);
- if (!NT_STATUS_IS_OK(status)) {
- if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
- goto err;
- }
- goto fail;
- }
- if (state->done) {
- goto done;
- }
+ *_start = start;
+
+ if (end[0] == '/') {
+ end += 1;
}
+ *_next_component = end;
- /*
- * Cache the full path. Don't cache a name with mangled or wildcard
- * components as this can change the size.
- */
+ return true;
+}
+
+bool extract_snapshot_token(char *fname, NTTIME *twrp)
+{
+ const char *start = NULL;
+ const char *next = NULL;
+ size_t remaining;
+ bool found;
- if(!state->component_was_mangled) {
- stat_cache_add(state->orig_path,
- state->smb_fname->base_name,
- state->smb_fname->twrp,
- state->case_sensitive);
+ found = find_snapshot_token(fname, &start, &next, twrp);
+ if (!found) {
+ return false;
}
- /*
- * The name has been resolved.
- */
+ remaining = strlen(next);
+ memmove(discard_const_p(char, start), next, remaining+1);
- done:
- /* Add back the stream if one was stripped off originally. */
- if (state->stream != NULL) {
- state->smb_fname->stream_name = state->stream;
+ return true;
+}
- /* Check path now that the base_name has been converted. */
- status = build_stream_path(state->mem_ctx, state->conn, state->smb_fname);
- if (!NT_STATUS_IS_OK(status)) {
- goto fail;
- }
+/*
+ * Strip a valid @GMT-token from any incoming filename path,
+ * adding any NTTIME encoded in the pathname into the
+ * twrp field of the passed in smb_fname.
+ *
+ * Valid @GMT-tokens look like @GMT-YYYY-MM-DD-HH-MM-SS
+ * at the *start* of a pathname component.
+ *
+ * If twrp is passed in then smb_fname->twrp is set to that
+ * value, and the @GMT-token part of the filename is removed
+ * and does not change the stored smb_fname->twrp.
+ *
+ */
+
+NTSTATUS canonicalize_snapshot_path(struct smb_filename *smb_fname,
+ uint32_t ucf_flags,
+ NTTIME twrp)
+{
+ bool found;
+
+ if (twrp != 0) {
+ smb_fname->twrp = twrp;
}
- DBG_DEBUG("Conversion finished [%s] -> [%s]\n",
- state->orig_path, smb_fname_str_dbg(state->smb_fname));
+ if (!(ucf_flags & UCF_GMT_PATHNAME)) {
+ return NT_STATUS_OK;
+ }
- TALLOC_FREE(state->dirpath);
- *smb_fname_out = state->smb_fname;
- return NT_STATUS_OK;
- fail:
- DBG_DEBUG("Conversion failed: dirpath [%s] name [%s]\n",
- state->dirpath, state->name);
- if ((state->dirpath != NULL) && !ISDOT(state->dirpath)) {
- state->smb_fname->base_name = talloc_asprintf(
- state->smb_fname,
- "%s/%s",
- state->dirpath,
- state->name);
- } else {
- state->smb_fname->base_name = talloc_strdup(
- state->smb_fname, state->name);
+ found = extract_snapshot_token(smb_fname->base_name, &twrp);
+ if (!found) {
+ return NT_STATUS_OK;
}
- if (state->smb_fname->base_name == NULL) {
- DBG_ERR("talloc_asprintf failed\n");
- status = NT_STATUS_NO_MEMORY;
- goto err;
+
+ if (smb_fname->twrp == 0) {
+ smb_fname->twrp = twrp;
}
- *smb_fname_out = state->smb_fname;
- TALLOC_FREE(state->dirpath);
- return status;
- err:
- TALLOC_FREE(state->smb_fname);
- return status;
+ return NT_STATUS_OK;
}
-/****************************************************************************
- Ensure a path is not vetoed.
-****************************************************************************/
-
-static NTSTATUS check_veto_path(connection_struct *conn,
- const struct smb_filename *smb_fname)
+static bool strnorm(char *s, int case_default)
{
- const char *name = smb_fname->base_name;
-
- if (IS_VETO_PATH(conn, name)) {
- /* Is it not dot or dot dot. */
- 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 (case_default == CASE_UPPER)
+ return strupper_m(s);
+ else
+ return strlower_m(s);
}
-/****************************************************************************
- 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.
-****************************************************************************/
+/*
+ * Utility function to normalize case on an incoming client filename
+ * if required on this connection struct.
+ * Performs an in-place case conversion guaranteed to stay the same size.
+ */
-static NTSTATUS check_name(connection_struct *conn,
- const struct smb_filename *smb_fname)
+static NTSTATUS normalize_filename_case(connection_struct *conn,
+ char *filename,
+ uint32_t ucf_flags)
{
- NTSTATUS status = check_veto_path(conn, smb_fname);
+ bool ok;
- if (!NT_STATUS_IS_OK(status)) {
- return status;
+ if (ucf_flags & UCF_POSIX_PATHNAMES) {
+ /*
+ * POSIX never normalizes filename case.
+ */
+ return NT_STATUS_OK;
}
-
- 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",
- smb_fname->base_name,
- nt_errstr(status)));
- return status;
- }
+ if (!conn->case_sensitive) {
+ return NT_STATUS_OK;
+ }
+ if (conn->case_preserve) {
+ return NT_STATUS_OK;
+ }
+ if (conn->short_case_preserve) {
+ return NT_STATUS_OK;
+ }
+ ok = strnorm(filename, lp_default_case(SNUM(conn)));
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
}
-
return NT_STATUS_OK;
}
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
-NTSTATUS get_real_filename_full_scan(connection_struct *conn,
- const char *path,
- const char *name,
- bool mangled,
- TALLOC_CTX *mem_ctx,
- char **found_name)
-{
- struct smb_filename *smb_dname = NULL;
- NTSTATUS status;
-
- /* handle null paths */
- if ((path == NULL) || (*path == 0)) {
- path = ".";
- }
-
- status = synthetic_pathref(
- talloc_tos(),
- conn->cwd_fsp,
- path,
- NULL,
- NULL,
- 0,
- 0,
- &smb_dname);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
-
- status = get_real_filename_full_scan_at(
- smb_dname->fsp, name, mangled, mem_ctx, found_name);
-
- TALLOC_FREE(smb_dname);
- return status;
-}
-
/****************************************************************************
Wrapper around the vfs get_real_filename and the full directory scan
fallback.
return true;
}
-static NTSTATUS get_real_filename(connection_struct *conn,
- struct smb_filename *path,
- const char *name,
- TALLOC_CTX *mem_ctx,
- char **found_name)
-{
- struct smb_filename *smb_dname = NULL;
- NTSTATUS status;
-
- smb_dname = cp_smb_filename_nostream(talloc_tos(), path);
- if (smb_dname == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
-
-again:
- status = openat_pathref_fsp(conn->cwd_fsp, smb_dname);
-
- if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
- S_ISLNK(smb_dname->st.st_ex_mode)) {
- status = NT_STATUS_STOPPED_ON_SYMLINK;
- }
-
- if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
- (smb_dname->twrp != 0)) {
- /*
- * Retry looking at the non-snapshot path, copying the
- * fallback mechanism from vfs_shadow_copy2.c when
- * shadow_copy2_convert() fails. This path-based
- * routine get_real_filename() should go away and be
- * replaced with a fd-based one, so spoiling it with a
- * shadow_copy2 specific mechanism should not be too
- * bad.
- */
- smb_dname->twrp = 0;
- goto again;
- }
-
- if (!NT_STATUS_IS_OK(status)) {
- DBG_DEBUG("openat_pathref_fsp(%s) failed: %s\n",
- smb_fname_str_dbg(smb_dname),
- nt_errstr(status));
-
- /*
- * ENOTDIR and ELOOP both map to
- * NT_STATUS_OBJECT_PATH_NOT_FOUND in the filename
- * walk.
- */
- if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_A_DIRECTORY) ||
- NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
- status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
- }
-
- return status;
- }
-
- status = get_real_filename_at(
- smb_dname->fsp, name, mem_ctx, found_name);
- TALLOC_FREE(smb_dname);
- return status;
-}
-
-static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
- connection_struct *conn,
- struct smb_filename *smb_fname)
-{
- NTSTATUS status;
- unsigned int i, num_streams = 0;
- struct stream_struct *streams = NULL;
- struct smb_filename *pathref = NULL;
-
- if (SMB_VFS_STAT(conn, smb_fname) == 0) {
- DEBUG(10, ("'%s' exists\n", smb_fname_str_dbg(smb_fname)));
- return NT_STATUS_OK;
- }
-
- if (errno != ENOENT) {
- DEBUG(10, ("vfs_stat failed: %s\n", strerror(errno)));
- status = map_nt_error_from_unix(errno);
- goto fail;
- }
-
- if (smb_fname->fsp == NULL) {
- status = synthetic_pathref(mem_ctx,
- conn->cwd_fsp,
- smb_fname->base_name,
- NULL,
- NULL,
- smb_fname->twrp,
- smb_fname->flags,
- &pathref);
- if (!NT_STATUS_IS_OK(status)) {
- if (NT_STATUS_EQUAL(status,
- NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
- TALLOC_FREE(pathref);
- SET_STAT_INVALID(smb_fname->st);
- return NT_STATUS_OK;
- }
- DBG_DEBUG("synthetic_pathref failed: %s\n",
- nt_errstr(status));
- goto fail;
- }
- } else {
- pathref = smb_fname;
- }
-
- /* Fall back to a case-insensitive scan of all streams on the file. */
- status = vfs_fstreaminfo(pathref->fsp, mem_ctx,
- &num_streams, &streams);
- if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
- SET_STAT_INVALID(smb_fname->st);
- TALLOC_FREE(pathref);
- return NT_STATUS_OK;
- }
-
- if (!NT_STATUS_IS_OK(status)) {
- DEBUG(10, ("vfs_fstreaminfo failed: %s\n", nt_errstr(status)));
- goto fail;
- }
-
- for (i=0; i<num_streams; i++) {
- bool equal = sname_equal(
- smb_fname->stream_name,
- streams[i].name,
- conn->case_sensitive);
-
- DBG_DEBUG("comparing [%s] and [%s]: %sequal\n",
- smb_fname->stream_name,
- streams[i].name,
- equal ? "" : "not ");
-
- if (equal) {
- break;
- }
- }
-
- /* Couldn't find the stream. */
- if (i == num_streams) {
- SET_STAT_INVALID(smb_fname->st);
- TALLOC_FREE(pathref);
- TALLOC_FREE(streams);
- return NT_STATUS_OK;
- }
-
- DEBUG(10, ("case insensitive stream. requested: %s, actual: %s\n",
- smb_fname->stream_name, streams[i].name));
-
-
- TALLOC_FREE(smb_fname->stream_name);
- smb_fname->stream_name = talloc_strdup(smb_fname, streams[i].name);
- if (smb_fname->stream_name == NULL) {
- status = NT_STATUS_NO_MEMORY;
- goto fail;
- }
-
- SET_STAT_INVALID(smb_fname->st);
-
- if (SMB_VFS_STAT(conn, smb_fname) == 0) {
- DEBUG(10, ("'%s' exists\n", smb_fname_str_dbg(smb_fname)));
- }
- status = NT_STATUS_OK;
- fail:
- TALLOC_FREE(pathref);
- TALLOC_FREE(streams);
- return status;
-}
-
/*
* Lightweight function to just get last component
* for rename / enumerate directory calls.