diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source3/smbd/filename.c | |
parent | Initial commit. (diff) | |
download | samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip |
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/smbd/filename.c')
-rw-r--r-- | source3/smbd/filename.c | 1271 |
1 files changed, 1271 insertions, 0 deletions
diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c new file mode 100644 index 0000000..f80f67c --- /dev/null +++ b/source3/smbd/filename.c @@ -0,0 +1,1271 @@ +/* + Unix SMB/CIFS implementation. + filename handling routines + Copyright (C) Andrew Tridgell 1992-1998 + Copyright (C) Jeremy Allison 1999-2007 + Copyright (C) Ying Chen 2000 + Copyright (C) Volker Lendecke 2007 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * New hash table stat cache code added by Ying Chen. + */ + +#include "includes.h" +#include "system/filesys.h" +#include "fake_file.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "libcli/smb/reparse.h" +#include "source3/smbd/dir.h" + +uint32_t ucf_flags_from_smb_request(struct smb_request *req) +{ + uint32_t ucf_flags = 0; + + if (req == NULL) { + return 0; + } + + if (req->posix_pathnames) { + ucf_flags |= UCF_POSIX_PATHNAMES; + + if (!req->sconn->using_smb2) { + ucf_flags |= UCF_LCOMP_LNK_OK; + } + } + 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; +} + +/**************************************************************************** + Mangle the 2nd name and check if it is then equal to the first name. +****************************************************************************/ + +static bool mangled_equal(const char *name1, + const char *name2, + const struct share_params *p) +{ + char mname[13]; + + if (!name_to_8_3(name2, mname, False, p)) { + return False; + } + return strequal(name1, mname); +} + +/* + * 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; +} + +/**************************************************************************** + Check if two filenames are equal. + This needs to be careful about whether we are case sensitive. +****************************************************************************/ + +static bool fname_equal(const char *name1, const char *name2, + bool case_sensitive) +{ + /* Normal filename handling */ + if (case_sensitive) { + return(strcmp(name1,name2) == 0); + } + + return(strequal(name1,name2)); +} + +static bool sname_equal(const char *name1, const char *name2, + bool case_sensitive) +{ + bool match; + const char *s1 = NULL; + const char *s2 = NULL; + size_t n1; + size_t n2; + const char *e1 = NULL; + const char *e2 = NULL; + char *c1 = NULL; + char *c2 = NULL; + + match = fname_equal(name1, name2, case_sensitive); + if (match) { + return true; + } + + if (name1[0] != ':') { + return false; + } + if (name2[0] != ':') { + return false; + } + s1 = &name1[1]; + e1 = strchr(s1, ':'); + if (e1 == NULL) { + n1 = strlen(s1); + } else { + n1 = PTR_DIFF(e1, s1); + } + s2 = &name2[1]; + e2 = strchr(s2, ':'); + if (e2 == NULL) { + n2 = strlen(s2); + } else { + n2 = PTR_DIFF(e2, s2); + } + + /* Normal filename handling */ + if (case_sensitive) { + return (strncmp(s1, s2, n1) == 0); + } + + /* + * We can't use strnequal() here + * as it takes the number of codepoints + * and not the number of bytes. + * + * So we make a copy before calling + * strequal(). + * + * Note that we TALLOC_FREE() in reverse order + * in order to avoid memory fragmentation. + */ + + c1 = talloc_strndup(talloc_tos(), s1, n1); + c2 = talloc_strndup(talloc_tos(), s2, n2); + if (c1 == NULL || c2 == NULL) { + TALLOC_FREE(c2); + TALLOC_FREE(c1); + return (strncmp(s1, s2, n1) == 0); + } + + match = strequal(c1, c2); + TALLOC_FREE(c2); + TALLOC_FREE(c1); + return match; +} + +/**************************************************************************** + Scan a directory to find a filename, matching without case sensitivity. + If the name looks like a mangled name then try via the mangling functions +****************************************************************************/ + +NTSTATUS get_real_filename_full_scan_at(struct files_struct *dirfsp, + const char *name, + bool mangled, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + struct connection_struct *conn = dirfsp->conn; + struct smb_Dir *cur_dir = NULL; + const char *dname = NULL; + char *talloced = NULL; + char *unmangled_name = NULL; + NTSTATUS status; + + /* If we have a case-sensitive filesystem, it doesn't do us any + * good to search for a name. If a case variation of the name was + * there, then the original stat(2) would have found it. + */ + if (!mangled && !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* + * The incoming name can be mangled, and if we de-mangle it + * here it will not compare correctly against the filename (name2) + * read from the directory and then mangled by the name_to_8_3() + * call. We need to mangle both names or neither. + * (JRA). + * + * Fix for bug found by Dina Fine. If in case sensitive mode then + * the mangle cache is no good (3 letter extension could be wrong + * case - so don't demangle in this case - leave as mangled and + * allow the mangling of the directory entry read (which is done + * case insensitively) to match instead. This will lead to more + * false positive matches but we fail completely without it. JRA. + */ + + if (mangled && !conn->case_sensitive) { + mangled = !mangle_lookup_name_from_8_3(talloc_tos(), name, + &unmangled_name, + conn->params); + if (!mangled) { + /* Name is now unmangled. */ + name = unmangled_name; + } + } + + /* open the directory */ + status = OpenDir_from_pathref(talloc_tos(), dirfsp, NULL, 0, &cur_dir); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("scan dir didn't open dir [%s]: %s\n", + fsp_str_dbg(dirfsp), + nt_errstr(status)); + TALLOC_FREE(unmangled_name); + return status; + } + + /* now scan for matching names */ + while ((dname = ReadDirName(cur_dir, &talloced))) { + + /* Is it dot or dot dot. */ + if (ISDOT(dname) || ISDOTDOT(dname)) { + TALLOC_FREE(talloced); + continue; + } + + /* + * At this point dname is the unmangled name. + * name is either mangled or not, depending on the state + * of the "mangled" variable. JRA. + */ + + /* + * Check mangled name against mangled name, or unmangled name + * against unmangled name. + */ + + if ((mangled && mangled_equal(name,dname,conn->params)) || + fname_equal(name, dname, conn->case_sensitive)) { + /* we've found the file, change it's name and return */ + *found_name = talloc_strdup(mem_ctx, dname); + TALLOC_FREE(unmangled_name); + TALLOC_FREE(cur_dir); + if (!*found_name) { + TALLOC_FREE(talloced); + return NT_STATUS_NO_MEMORY; + } + TALLOC_FREE(talloced); + return NT_STATUS_OK; + } + TALLOC_FREE(talloced); + } + + TALLOC_FREE(unmangled_name); + TALLOC_FREE(cur_dir); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; +} + +/**************************************************************************** + Wrapper around the vfs get_real_filename and the full directory scan + fallback. +****************************************************************************/ + +NTSTATUS get_real_filename_at(struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + struct connection_struct *conn = dirfsp->conn; + NTSTATUS status; + bool mangled; + + mangled = mangle_is_mangled(name, conn->params); + + if (mangled) { + status = get_real_filename_full_scan_at( + dirfsp, name, mangled, mem_ctx, found_name); + return status; + } + + /* Try the vfs first to take advantage of case-insensitive stat. */ + status = SMB_VFS_GET_REAL_FILENAME_AT( + dirfsp->conn, dirfsp, name, mem_ctx, found_name); + + /* + * If the case-insensitive stat was successful, or returned an error + * other than EOPNOTSUPP then there is no need to fall back on the + * full directory scan. + */ + if (NT_STATUS_IS_OK(status) || + !NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + return status; + } + + status = get_real_filename_full_scan_at( + dirfsp, name, mangled, mem_ctx, found_name); + return status; +} + +/* + * Lightweight function to just get last component + * for rename / enumerate directory calls. + */ + +char *get_original_lcomp(TALLOC_CTX *ctx, + connection_struct *conn, + const char *filename_in, + uint32_t ucf_flags) +{ + char *last_slash = NULL; + char *orig_lcomp; + NTSTATUS status; + + last_slash = strrchr(filename_in, '/'); + if (last_slash != NULL) { + orig_lcomp = talloc_strdup(ctx, last_slash+1); + } else { + orig_lcomp = talloc_strdup(ctx, filename_in); + } + if (orig_lcomp == NULL) { + return NULL; + } + status = normalize_filename_case(conn, orig_lcomp, ucf_flags); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(orig_lcomp); + return NULL; + } + return orig_lcomp; +} + +/* + * Get the correct capitalized stream name hanging off + * base_fsp. Equivalent of get_real_filename(), but for streams. + */ +static NTSTATUS get_real_stream_name( + TALLOC_CTX *mem_ctx, + struct files_struct *base_fsp, + const char *stream_name, + char **_found) +{ + unsigned int i, num_streams = 0; + struct stream_struct *streams = NULL; + NTSTATUS status; + + status = vfs_fstreaminfo( + base_fsp, talloc_tos(), &num_streams, &streams); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + for (i=0; i<num_streams; i++) { + bool equal = sname_equal(stream_name, streams[i].name, false); + + DBG_DEBUG("comparing [%s] and [%s]: %sequal\n", + stream_name, + streams[i].name, + equal ? "" : "not "); + + if (equal) { + *_found = talloc_move(mem_ctx, &streams[i].name); + TALLOC_FREE(streams); + return NT_STATUS_OK; + } + } + + TALLOC_FREE(streams); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; +} + +static bool filename_split_lcomp( + TALLOC_CTX *mem_ctx, + const char *name_in, + bool posix, + char **_dirname, + const char **_fname_rel, + const char **_streamname) +{ + const char *lcomp = NULL; + const char *fname_rel = NULL; + const char *streamname = NULL; + char *dirname = NULL; + + if (name_in[0] == '\0') { + fname_rel = "."; + dirname = talloc_strdup(mem_ctx, ""); + if (dirname == NULL) { + return false; + } + goto done; + } + + lcomp = strrchr_m(name_in, '/'); + if (lcomp != NULL) { + fname_rel = lcomp+1; + dirname = talloc_strndup(mem_ctx, name_in, lcomp - name_in); + if (dirname == NULL) { + return false; + } + goto find_stream; + } + + /* + * No slash, dir is empty + */ + dirname = talloc_strdup(mem_ctx, ""); + if (dirname == NULL) { + return false; + } + + if (!posix && (name_in[0] == ':')) { + /* + * Special case for stream on root directory + */ + fname_rel = "."; + streamname = name_in; + goto done; + } + + fname_rel = name_in; + +find_stream: + if (!posix) { + streamname = strchr_m(fname_rel, ':'); + + if (streamname != NULL) { + fname_rel = talloc_strndup( + mem_ctx, + fname_rel, + streamname - fname_rel); + if (fname_rel == NULL) { + TALLOC_FREE(dirname); + return false; + } + } + } + +done: + *_dirname = dirname; + *_fname_rel = fname_rel; + *_streamname = streamname; + return true; +} + +/* + * Create the correct capitalization of a file name to be created. + */ +static NTSTATUS filename_convert_normalize_new( + TALLOC_CTX *mem_ctx, + struct connection_struct *conn, + char *name_in, + char **_normalized) +{ + char *name = name_in; + + *_normalized = NULL; + + if (!conn->case_preserve || + (mangle_is_8_3(name, false, + conn->params) && + !conn->short_case_preserve)) { + + char *normalized = talloc_strdup(mem_ctx, name); + if (normalized == NULL) { + return NT_STATUS_NO_MEMORY; + } + + strnorm(normalized, lp_default_case(SNUM(conn))); + name = normalized; + } + + if (mangle_is_mangled(name, conn->params)) { + bool found; + char *unmangled = NULL; + + found = mangle_lookup_name_from_8_3( + mem_ctx, name, &unmangled, conn->params); + if (found) { + name = unmangled; + } + } + + if (name != name_in) { + *_normalized = name; + } + + return NT_STATUS_OK; +} + +static const char *previous_slash(const char *name_in, const char *slash) +{ + const char *prev = NULL; + + SMB_ASSERT((name_in <= slash) && (slash[0] == '/')); + + prev = strchr_m(name_in, '/'); + + if (prev == slash) { + /* No previous slash */ + return NULL; + } + + while (true) { + const char *next = strchr_m(prev + 1, '/'); + + if (next == slash) { + return prev; + } + prev = next; + } + + return NULL; /* unreachable */ +} + +static char *symlink_target_path( + TALLOC_CTX *mem_ctx, + const char *name_in, + const char *substitute, + size_t unparsed) +{ + size_t name_in_len = strlen(name_in); + const char *p_unparsed = NULL; + const char *parent = NULL; + char *ret; + + SMB_ASSERT(unparsed <= name_in_len); + + p_unparsed = name_in + (name_in_len - unparsed); + + if (substitute[0] == '/') { + ret = talloc_asprintf(mem_ctx, "%s%s", substitute, p_unparsed); + return ret; + } + + if (unparsed == 0) { + parent = strrchr_m(name_in, '/'); + } else { + parent = previous_slash(name_in, p_unparsed); + } + + if (parent == NULL) { + ret = talloc_asprintf(mem_ctx, "%s%s", substitute, p_unparsed); + } else { + ret = talloc_asprintf(mem_ctx, + "%.*s/%s%s", + (int)(parent - name_in), + name_in, + substitute, + p_unparsed); + } + + return ret; +} + +NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx, + const char *connectpath, + const char *dir, + const char *target, + size_t unparsed, + char **_relative) +{ + char *abs_target = NULL; + char *abs_target_canon = NULL; + const char *relative = NULL; + bool in_share; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + DBG_DEBUG("connectpath [%s] target [%s] unparsed [%zu]\n", + connectpath, target, unparsed); + + if (target[0] == '/') { + abs_target = talloc_strdup(mem_ctx, target); + } else if (dir == NULL) { + abs_target = talloc_asprintf(mem_ctx, + "%s/%s", + connectpath, + target); + } else if (dir[0] == '/') { + abs_target = talloc_asprintf(mem_ctx, + "%s/%s", + dir, + target); + } else { + abs_target = talloc_asprintf(mem_ctx, + "%s/%s/%s", + connectpath, + dir, + target); + } + if (abs_target == NULL) { + goto fail; + } + + abs_target_canon = canonicalize_absolute_path(abs_target, abs_target); + if (abs_target_canon == NULL) { + goto fail; + } + + DBG_DEBUG("abs_target_canon=%s\n", abs_target_canon); + + in_share = subdir_of( + connectpath, strlen(connectpath), abs_target_canon, &relative); + if (!in_share) { + DBG_DEBUG("wide link to %s\n", abs_target_canon); + status = (unparsed != 0) ? NT_STATUS_OBJECT_PATH_NOT_FOUND + : NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto fail; + } + + *_relative = talloc_strdup(mem_ctx, relative); + if (*_relative == NULL) { + goto fail; + } + + status = NT_STATUS_OK; +fail: + TALLOC_FREE(abs_target); + return status; +} + +/* + * Split up name_in as sent by the client into a directory pathref fsp + * and a relative smb_filename. + */ +static NTSTATUS filename_convert_dirfsp_nosymlink( + TALLOC_CTX *mem_ctx, + connection_struct *conn, + const char *name_in, + uint32_t ucf_flags, + NTTIME twrp, + struct files_struct **_dirfsp, + struct smb_filename **_smb_fname, + struct open_symlink_err **_symlink_err) +{ + struct smb_filename *smb_dirname = NULL; + struct smb_filename *smb_fname_rel = NULL; + struct smb_filename *smb_fname = NULL; + struct open_symlink_err *symlink_err = NULL; + const bool posix = (ucf_flags & UCF_POSIX_PATHNAMES); + char *dirname = NULL; + const char *fname_rel = NULL; + const char *streamname = NULL; + char *saved_streamname = NULL; + struct files_struct *base_fsp = NULL; + bool ok; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + SMB_ASSERT(!(ucf_flags & UCF_DFS_PATHNAME)); + + if (is_fake_file_path(name_in)) { + smb_fname = synthetic_smb_fname_split(mem_ctx, name_in, posix); + if (smb_fname == NULL) { + return NT_STATUS_NO_MEMORY; + } + smb_fname->st = (SMB_STRUCT_STAT){ + .st_ex_nlink = 1, + .st_ex_mode = S_IFREG | 0644, + }; + smb_fname->st.st_ex_btime = + (struct timespec){0, SAMBA_UTIME_OMIT}; + smb_fname->st.st_ex_atime = + (struct timespec){0, SAMBA_UTIME_OMIT}; + smb_fname->st.st_ex_mtime = + (struct timespec){0, SAMBA_UTIME_OMIT}; + smb_fname->st.st_ex_ctime = + (struct timespec){0, SAMBA_UTIME_OMIT}; + + *_dirfsp = conn->cwd_fsp; + *_smb_fname = smb_fname; + return NT_STATUS_OK; + } + + /* + * Catch an invalid path of "." before we + * call filename_split_lcomp(). We need to + * do this as filename_split_lcomp() will + * use "." for the missing relative component + * when an empty name_in path is sent by + * the client. + */ + if (ISDOT(name_in)) { + status = NT_STATUS_OBJECT_NAME_INVALID; + goto fail; + } + + ok = filename_split_lcomp( + talloc_tos(), + name_in, + posix, + &dirname, + &fname_rel, + &streamname); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if ((streamname != NULL) && + ((conn->fs_capabilities & FILE_NAMED_STREAMS) == 0)) { + status = NT_STATUS_OBJECT_NAME_INVALID; + goto fail; + } + + if (!posix) { + bool name_has_wild = ms_has_wild(dirname); + name_has_wild |= ms_has_wild(fname_rel); + if (name_has_wild) { + status = NT_STATUS_OBJECT_NAME_INVALID; + goto fail; + } + } + + if (dirname[0] == '\0') { + status = synthetic_pathref( + mem_ctx, + conn->cwd_fsp, + ".", + NULL, + NULL, + 0, + posix ? SMB_FILENAME_POSIX_PATH : 0, + &smb_dirname); + } else { + status = normalize_filename_case(conn, dirname, ucf_flags); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("normalize_filename_case %s failed: %s\n", + dirname, + nt_errstr(status)); + goto fail; + } + + status = openat_pathref_fsp_nosymlink(mem_ctx, + conn, + conn->cwd_fsp, + dirname, + twrp, + posix, + &smb_dirname, + &symlink_err); + + if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) { + size_t name_in_len, dirname_len; + + name_in_len = strlen(name_in); + dirname_len = strlen(dirname); + + SMB_ASSERT(name_in_len >= dirname_len); + + symlink_err->unparsed += (name_in_len - dirname_len); + *_symlink_err = symlink_err; + + goto fail; + } + } + + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("opening directory %s failed: %s\n", + dirname, + nt_errstr(status)); + TALLOC_FREE(dirname); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + /* + * Except ACCESS_DENIED, everything else leads + * to PATH_NOT_FOUND. + */ + status = NT_STATUS_OBJECT_PATH_NOT_FOUND; + } + + goto fail; + } + + if (!VALID_STAT_OF_DIR(smb_dirname->st)) { + status = NT_STATUS_OBJECT_PATH_NOT_FOUND; + goto fail; + } + smb_dirname->fsp->fsp_flags.is_directory = true; + + /* + * Only look at bad last component values + * once we know we have a valid directory. That + * way we won't confuse error messages from + * opening the directory path with error + * messages from a bad last component. + */ + + /* Relative filename can't be empty */ + if (fname_rel[0] == '\0') { + status = NT_STATUS_OBJECT_NAME_INVALID; + goto fail; + } + + /* Relative filename can't be ".." */ + if (ISDOTDOT(fname_rel)) { + status = NT_STATUS_OBJECT_NAME_INVALID; + goto fail; + } + /* Relative name can only be dot if directory is empty. */ + if (ISDOT(fname_rel) && dirname[0] != '\0') { + status = NT_STATUS_OBJECT_NAME_INVALID; + goto fail; + } + + TALLOC_FREE(dirname); + + smb_fname_rel = synthetic_smb_fname( + mem_ctx, + fname_rel, + streamname, + NULL, + twrp, + posix ? SMB_FILENAME_POSIX_PATH : 0); + if (smb_fname_rel == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if ((conn->fs_capabilities & FILE_NAMED_STREAMS) && + is_named_stream(smb_fname_rel)) { + /* + * Find the base_fsp first without the stream. + */ + saved_streamname = smb_fname_rel->stream_name; + smb_fname_rel->stream_name = NULL; + } + + status = normalize_filename_case( + conn, smb_fname_rel->base_name, ucf_flags); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("normalize_filename_case %s failed: %s\n", + smb_fname_rel->base_name, + nt_errstr(status)); + goto fail; + } + + status = openat_pathref_fsp_lcomp(smb_dirname->fsp, + smb_fname_rel, + ucf_flags); + + if (NT_STATUS_IS_OK(status) && S_ISLNK(smb_fname_rel->st.st_ex_mode)) { + + /* + * Upper layers might need the link target. Here we + * still have the relname around, get the symlink err. + */ + status = create_open_symlink_err(mem_ctx, + smb_dirname->fsp, + smb_fname_rel, + &symlink_err); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Could not read symlink for %s: %s\n", + smb_fname_str_dbg( + smb_fname_rel->fsp->fsp_name), + nt_errstr(status)); + goto fail; + } + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) && + !VALID_STAT(smb_fname_rel->st)) { + + char *normalized = NULL; + + /* + * Creating a new file + */ + + status = filename_convert_normalize_new( + smb_fname_rel, + conn, + smb_fname_rel->base_name, + &normalized); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("filename_convert_normalize_new failed: " + "%s\n", + nt_errstr(status)); + goto fail; + } + if (normalized != NULL) { + smb_fname_rel->base_name = normalized; + } + + smb_fname_rel->stream_name = saved_streamname; + + smb_fname = full_path_from_dirfsp_atname( + mem_ctx, smb_dirname->fsp, smb_fname_rel); + if (smb_fname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + goto done; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_OPEN_RESTRICTION)) { + /* A vetoed file, pretend it's not there */ + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + if (saved_streamname == NULL) { + /* smb_fname must be allocated off mem_ctx. */ + smb_fname = cp_smb_filename(mem_ctx, + smb_fname_rel->fsp->fsp_name); + if (smb_fname == NULL) { + goto fail; + } + status = move_smb_fname_fsp_link(smb_fname, smb_fname_rel); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + goto done; + } + + base_fsp = smb_fname_rel->fsp; + smb_fname_fsp_unlink(smb_fname_rel); + SET_STAT_INVALID(smb_fname_rel->st); + + smb_fname_rel->stream_name = saved_streamname; + + status = open_stream_pathref_fsp(&base_fsp, smb_fname_rel); + + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) && + !conn->case_sensitive) { + char *found = NULL; + + status = get_real_stream_name( + smb_fname_rel, + base_fsp, + smb_fname_rel->stream_name, + &found); + + if (NT_STATUS_IS_OK(status)) { + smb_fname_rel->stream_name = found; + found = NULL; + status = open_stream_pathref_fsp( + &base_fsp, smb_fname_rel); + } + } + + if (NT_STATUS_IS_OK(status)) { + /* smb_fname must be allocated off mem_ctx. */ + smb_fname = cp_smb_filename(mem_ctx, + smb_fname_rel->fsp->fsp_name); + if (smb_fname == NULL) { + goto fail; + } + status = move_smb_fname_fsp_link(smb_fname, smb_fname_rel); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + goto done; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + /* + * Creating a new stream + * + * We should save the already-open base fsp for + * create_file_unixpath() somehow. + */ + smb_fname = full_path_from_dirfsp_atname( + mem_ctx, smb_dirname->fsp, smb_fname_rel); + if (smb_fname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + /* + * When open_stream_pathref_fsp() returns + * NT_STATUS_OBJECT_NAME_NOT_FOUND, smb_fname_rel->fsp + * has been set to NULL, so we must free base_fsp separately + * to prevent fd-leaks when opening a stream that doesn't + * exist. + */ + fd_close(base_fsp); + file_free(NULL, base_fsp); + base_fsp = NULL; + goto done; + } + + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + +done: + *_dirfsp = smb_dirname->fsp; + *_smb_fname = smb_fname; + *_symlink_err = symlink_err; + + smb_fname_fsp_unlink(smb_fname_rel); + TALLOC_FREE(smb_fname_rel); + return NT_STATUS_OK; + +fail: + /* + * If open_stream_pathref_fsp() returns an error, smb_fname_rel->fsp + * has been set to NULL, so we must free base_fsp separately + * to prevent fd-leaks when opening a stream that doesn't + * exist. + */ + if (base_fsp != NULL) { + fd_close(base_fsp); + file_free(NULL, base_fsp); + base_fsp = NULL; + } + TALLOC_FREE(dirname); + TALLOC_FREE(smb_dirname); + TALLOC_FREE(smb_fname_rel); + return status; +} + +NTSTATUS filename_convert_dirfsp( + TALLOC_CTX *mem_ctx, + connection_struct *conn, + const char *name_in, + uint32_t ucf_flags, + NTTIME twrp, + struct files_struct **_dirfsp, + struct smb_filename **_smb_fname) +{ + struct open_symlink_err *symlink_err = NULL; + NTSTATUS status; + char *target = NULL; + char *safe_target = NULL; + size_t symlink_redirects = 0; + +next: + if (symlink_redirects > 40) { + return NT_STATUS_OBJECT_PATH_NOT_FOUND; + } + + status = filename_convert_dirfsp_nosymlink(mem_ctx, + conn, + name_in, + ucf_flags, + twrp, + _dirfsp, + _smb_fname, + &symlink_err); + + if (NT_STATUS_IS_OK(status) && S_ISLNK((*_smb_fname)->st.st_ex_mode)) { + /* + * lcomp is a symlink + */ + if (ucf_flags & UCF_LCOMP_LNK_OK) { + TALLOC_FREE(symlink_err); + return NT_STATUS_OK; + } + close_file_free(NULL, _dirfsp, ERROR_CLOSE); + status = NT_STATUS_STOPPED_ON_SYMLINK; + } + + if (!NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) { + return status; + } + + /* + * If we're on an MSDFS share, see if this is + * an MSDFS link. + */ + if (lp_host_msdfs() && lp_msdfs_root(SNUM(conn)) && + strnequal(symlink_err->reparse->substitute_name, "msdfs:", 6)) + { + TALLOC_FREE(*_smb_fname); + TALLOC_FREE(symlink_err); + return NT_STATUS_PATH_NOT_COVERED; + } + + if (!lp_follow_symlinks(SNUM(conn))) { + status = (symlink_err->unparsed == 0) + ? NT_STATUS_OBJECT_NAME_NOT_FOUND + : NT_STATUS_OBJECT_PATH_NOT_FOUND; + TALLOC_FREE(symlink_err); + return status; + } + + /* + * Right now, SMB2 and SMB1 always traverse symlinks + * within the share. SMB1+POSIX traverses non-terminal + * symlinks within the share. + * + * When we add SMB2+POSIX we need to return + * a NT_STATUS_STOPPED_ON_SYMLINK error here, using the + * symlink target data read below if SMB2+POSIX has + * UCF_POSIX_PATHNAMES set to cause the client to + * resolve all symlinks locally. + */ + + target = symlink_target_path(mem_ctx, + name_in, + symlink_err->reparse->substitute_name, + symlink_err->unparsed); + if (target == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = safe_symlink_target_path(mem_ctx, + conn->connectpath, + NULL, + target, + symlink_err->unparsed, + &safe_target); + TALLOC_FREE(symlink_err); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + name_in = safe_target; + + symlink_redirects += 1; + + goto next; +} + +char *full_path_from_dirfsp_at_basename(TALLOC_CTX *mem_ctx, + const struct files_struct *dirfsp, + const char *at_base_name) +{ + char *path = NULL; + + if (dirfsp == dirfsp->conn->cwd_fsp || + ISDOT(dirfsp->fsp_name->base_name) || at_base_name[0] == '/') { + path = talloc_strdup(mem_ctx, at_base_name); + } else { + path = talloc_asprintf(mem_ctx, + "%s/%s", + dirfsp->fsp_name->base_name, + at_base_name); + } + + return path; +} + +/* + * Build the full path from a dirfsp and dirfsp relative name + */ +struct smb_filename * +full_path_from_dirfsp_atname(TALLOC_CTX *mem_ctx, + const struct files_struct *dirfsp, + const struct smb_filename *atname) +{ + struct smb_filename *fname = NULL; + char *path = NULL; + + path = full_path_from_dirfsp_at_basename(mem_ctx, + dirfsp, + atname->base_name); + if (path == NULL) { + return NULL; + } + + fname = synthetic_smb_fname(mem_ctx, + path, + atname->stream_name, + &atname->st, + atname->twrp, + atname->flags); + TALLOC_FREE(path); + if (fname == NULL) { + return NULL; + } + + return fname; +} |