From: Jeremy Allison Date: Tue, 9 Aug 2022 04:59:14 +0000 (-0700) Subject: s3: smbd: Remove unix_convert() and associated functions. X-Git-Tag: talloc-2.4.0~1445 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=78e4aac76df977cea6cdbcfdf082fd3acdffbd95;p=thirdparty%2Fsamba.git s3: smbd: Remove unix_convert() and associated functions. All code now uses filename_convert_dirfsp() for race-free filename conversion. Best viewed with: $ git show --patience ---------------- / \ / REST \ / IN \ / PEACE \ / \ | | | unix_convert | | | | | | 9th August | | 2022 | | | | | *| * * * | * _________)/\\_//(\/(/\)/\//\/\///\/|_)_______ BUG: https://bugzilla.samba.org/show_bug.cgi?id=15144 Signed-off-by: Jeremy Allison Reviewed-by: Ralph Boehme Autobuild-User(master): Jeremy Allison Autobuild-Date(master): Fri Aug 12 19:18:25 UTC 2022 on sn-devel-184 --- diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c index b64fb908f16..f362aee9452 100644 --- a/source3/smbd/filename.c +++ b/source3/smbd/filename.c @@ -31,15 +31,6 @@ #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; @@ -80,10 +71,6 @@ uint32_t filename_create_ucf_flags(struct smb_request *req, uint32_t create_disp 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. ****************************************************************************/ @@ -100,153 +87,6 @@ static bool mangled_equal(const char *name1, 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, @@ -255,1138 +95,148 @@ static bool find_snapshot_token( { 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 . - */ - 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; } @@ -1580,41 +430,6 @@ NTSTATUS get_real_filename_full_scan_at(struct files_struct *dirfsp, 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. @@ -1703,172 +518,6 @@ static bool get_real_filename_cache_key( 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; istream_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. diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h index bc305bce296..5ac0f713958 100644 --- a/source3/smbd/proto.h +++ b/source3/smbd/proto.h @@ -350,22 +350,10 @@ NTSTATUS sync_file(connection_struct *conn, files_struct *fsp, bool write_throug uint32_t ucf_flags_from_smb_request(struct smb_request *req); uint32_t filename_create_ucf_flags(struct smb_request *req, uint32_t create_disposition); -NTSTATUS unix_convert(TALLOC_CTX *ctx, - connection_struct *conn, - const char *orig_path, - NTTIME twrp, - struct smb_filename **smb_fname, - uint32_t ucf_flags); bool extract_snapshot_token(char *fname, NTTIME *twrp); NTSTATUS canonicalize_snapshot_path(struct smb_filename *smb_fname, uint32_t ucf_flags, NTTIME twrp); -NTSTATUS get_real_filename_full_scan(connection_struct *conn, - const char *path, - const char *name, - bool mangled, - TALLOC_CTX *mem_ctx, - char **found_name); NTSTATUS get_real_filename_full_scan_at(struct files_struct *dirfsp, const char *name, bool mangled,