summaryrefslogtreecommitdiffstats
path: root/source3/smbd/filename.c
diff options
context:
space:
mode:
Diffstat (limited to 'source3/smbd/filename.c')
-rw-r--r--source3/smbd/filename.c1607
1 files changed, 1607 insertions, 0 deletions
diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c
new file mode 100644
index 0000000..84e790a
--- /dev/null
+++ b/source3/smbd/filename.c
@@ -0,0 +1,1607 @@
+/*
+ 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 "lib/util/memcache.h"
+
+uint32_t ucf_flags_from_smb_request(struct smb_request *req)
+{
+ uint32_t ucf_flags = 0;
+
+ if (req != NULL) {
+ if (req->posix_pathnames) {
+ ucf_flags |= UCF_POSIX_PATHNAMES;
+ }
+ if (req->flags2 & FLAGS2_DFS_PATHNAMES) {
+ ucf_flags |= UCF_DFS_PATHNAME;
+ }
+ if (req->flags2 & FLAGS2_REPARSE_PATH) {
+ ucf_flags |= UCF_GMT_PATHNAME;
+ }
+ }
+
+ return ucf_flags;
+}
+
+uint32_t filename_create_ucf_flags(struct smb_request *req, uint32_t create_disposition)
+{
+ uint32_t ucf_flags = 0;
+
+ ucf_flags |= ucf_flags_from_smb_request(req);
+
+ switch (create_disposition) {
+ case FILE_OPEN:
+ case FILE_OVERWRITE:
+ break;
+ case FILE_SUPERSEDE:
+ case FILE_CREATE:
+ case FILE_OPEN_IF:
+ case FILE_OVERWRITE_IF:
+ ucf_flags |= UCF_PREP_CREATEFILE;
+ break;
+ }
+
+ return ucf_flags;
+}
+
+/****************************************************************************
+ 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);
+}
+
+static bool find_snapshot_token(
+ const char *filename,
+ const char **_start,
+ const char **_next_component,
+ NTTIME *twrp)
+{
+ 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;
+}
+
+/****************************************************************************
+ 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;
+ long curpos;
+ 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 */
+ curpos = 0;
+ while ((dname = ReadDirName(cur_dir, &curpos, NULL, &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;
+}
+
+/*
+ * Create the memcache-key for GETREALFILENAME_CACHE: This supplements
+ * the stat cache for the last component to be looked up. Cache
+ * contents is the correctly capitalized translation of the parameter
+ * "name" as it exists on disk. This is indexed by inode of the dirfsp
+ * and name, and contrary to stat_cahce_lookup() it does not
+ * vfs_stat() the last component. This will be taken care of by an
+ * attempt to do a openat_pathref_fsp().
+ */
+static bool get_real_filename_cache_key(
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const char *name,
+ DATA_BLOB *_key)
+{
+ struct file_id fid = vfs_file_id_from_sbuf(
+ dirfsp->conn, &dirfsp->fsp_name->st);
+ char *upper = NULL;
+ uint8_t *key = NULL;
+ size_t namelen, keylen;
+
+ upper = talloc_strdup_upper(mem_ctx, name);
+ if (upper == NULL) {
+ return false;
+ }
+ namelen = talloc_get_size(upper);
+
+ keylen = namelen + sizeof(fid);
+ if (keylen < sizeof(fid)) {
+ TALLOC_FREE(upper);
+ return false;
+ }
+
+ key = talloc_size(mem_ctx, keylen);
+ if (key == NULL) {
+ TALLOC_FREE(upper);
+ return false;
+ }
+
+ memcpy(key, &fid, sizeof(fid));
+ memcpy(key + sizeof(fid), upper, namelen);
+ TALLOC_FREE(upper);
+
+ *_key = (DATA_BLOB) { .data = key, .length = keylen, };
+ return true;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * Deal with the SMB1 semantics of sending a pathname with a
+ * wildcard as the terminal component for a SMB1search or
+ * trans2 findfirst.
+ */
+
+NTSTATUS filename_convert_smb1_search_path(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ char *name_in,
+ uint32_t ucf_flags,
+ struct files_struct **_dirfsp,
+ struct smb_filename **_smb_fname_out,
+ char **_mask_out)
+{
+ NTSTATUS status;
+ char *p = NULL;
+ char *mask = NULL;
+ struct smb_filename *smb_fname = NULL;
+ NTTIME twrp = 0;
+
+ *_smb_fname_out = NULL;
+ *_dirfsp = NULL;
+ *_mask_out = NULL;
+
+ DBG_DEBUG("name_in: %s\n", name_in);
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(name_in, &twrp);
+ ucf_flags &= ~UCF_GMT_PATHNAME;
+ }
+
+ if (ucf_flags & UCF_DFS_PATHNAME) {
+ /*
+ * We've been given a raw DFS pathname.
+ */
+ char *pathname = NULL;
+ DBG_DEBUG("Before dfs_filename_convert name_in: %s\n", name_in);
+ status = dfs_filename_convert(ctx,
+ conn,
+ ucf_flags,
+ name_in,
+ &pathname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dfs_filename_convert "
+ "failed for name %s with %s\n",
+ name_in,
+ nt_errstr(status));
+ return status;
+ }
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+ name_in = pathname;
+ DBG_DEBUG("After dfs_filename_convert name_in: %s\n", name_in);
+ }
+
+ /* Get the original lcomp. */
+ mask = get_original_lcomp(ctx,
+ conn,
+ name_in,
+ ucf_flags);
+ if (mask == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (mask[0] == '\0') {
+ /* Windows and OS/2 systems treat search on the root as * */
+ TALLOC_FREE(mask);
+ mask = talloc_strdup(ctx, "*");
+ if (mask == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ DBG_DEBUG("mask = %s\n", mask);
+
+ /*
+ * Remove the terminal component so
+ * filename_convert_dirfsp never sees the mask.
+ */
+ p = strrchr_m(name_in,'/');
+ if (p == NULL) {
+ /* filename_convert_dirfsp handles a '\0' name. */
+ name_in[0] = '\0';
+ } else {
+ *p = '\0';
+ }
+
+ DBG_DEBUG("For filename_convert_dirfsp: name_in = %s\n",
+ name_in);
+
+ /* Convert the parent directory path. */
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ name_in,
+ ucf_flags,
+ twrp,
+ _dirfsp,
+ &smb_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("filename_convert error for %s: %s\n",
+ name_in,
+ nt_errstr(status));
+ }
+
+ *_smb_fname_out = talloc_move(ctx, &smb_fname);
+ *_mask_out = talloc_move(ctx, &mask);
+
+ return status;
+}
+
+/*
+ * 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 emtpy
+ */
+ 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;
+}
+
+/*
+ * Open smb_fname_rel->fsp as a pathref fsp with a case insensitive
+ * fallback using GETREALFILENAME_CACHE and get_real_filename_at() if
+ * the first attempt based on the filename sent by the client gives
+ * ENOENT.
+ */
+static NTSTATUS openat_pathref_fsp_case_insensitive(
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname_rel,
+ uint32_t ucf_flags)
+{
+ const bool posix = (ucf_flags & UCF_POSIX_PATHNAMES);
+ DATA_BLOB cache_key = { .data = NULL, };
+ char *found_name = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ SET_STAT_INVALID(smb_fname_rel->st);
+
+ /* Check veto files - only looks at last component. */
+ if (IS_VETO_PATH(dirfsp->conn, smb_fname_rel->base_name)) {
+ DBG_DEBUG("veto files rejecting last component %s\n",
+ smb_fname_str_dbg(smb_fname_rel));
+ return NT_STATUS_NETWORK_OPEN_RESTRICTION;
+ }
+
+ status = openat_pathref_fsp(dirfsp, smb_fname_rel);
+
+ if (NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OK;
+ }
+
+ if (VALID_STAT(smb_fname_rel->st)) {
+ /*
+ * We got an error although the object existed. Might
+ * be a symlink we don't want.
+ */
+ return status;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * Only retry on ENOENT
+ */
+ return status;
+ }
+
+ if (posix || dirfsp->conn->case_sensitive) {
+ /*
+ * Only return case insensitive if required
+ */
+ return status;
+ }
+
+ if (lp_stat_cache()) {
+ char *base_name = smb_fname_rel->base_name;
+ DATA_BLOB value = { .data = NULL };
+
+ ok = get_real_filename_cache_key(
+ talloc_tos(), dirfsp, base_name, &cache_key);
+ if (!ok) {
+ /*
+ * probably ENOMEM, just bail
+ */
+ return status;
+ }
+
+ DO_PROFILE_INC(statcache_lookups);
+
+ ok = memcache_lookup(
+ NULL, GETREALFILENAME_CACHE, cache_key, &value);
+ if (!ok) {
+ DO_PROFILE_INC(statcache_misses);
+ goto lookup;
+ }
+ DO_PROFILE_INC(statcache_hits);
+
+ TALLOC_FREE(smb_fname_rel->base_name);
+ smb_fname_rel->base_name = talloc_memdup(
+ smb_fname_rel, value.data, value.length);
+ if (smb_fname_rel->base_name == NULL) {
+ TALLOC_FREE(cache_key.data);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (IS_VETO_PATH(dirfsp->conn, smb_fname_rel->base_name)) {
+ DBG_DEBUG("veto files rejecting last component %s\n",
+ smb_fname_str_dbg(smb_fname_rel));
+ TALLOC_FREE(cache_key.data);
+ return NT_STATUS_NETWORK_OPEN_RESTRICTION;
+ }
+
+ status = openat_pathref_fsp(dirfsp, smb_fname_rel);
+ if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(cache_key.data);
+ return NT_STATUS_OK;
+ }
+
+ memcache_delete(NULL, GETREALFILENAME_CACHE, cache_key);
+ }
+
+lookup:
+ status = get_real_filename_at(
+ dirfsp, smb_fname_rel->base_name, smb_fname_rel, &found_name);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
+ (ucf_flags & UCF_PREP_CREATEFILE)) {
+ /*
+ * dropbox
+ */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname_rel->base_name);
+ smb_fname_rel->base_name = found_name;
+
+ if (IS_VETO_PATH(dirfsp->conn, smb_fname_rel->base_name)) {
+ DBG_DEBUG("veto files rejecting last component %s\n",
+ smb_fname_str_dbg(smb_fname_rel));
+ return NT_STATUS_NETWORK_OPEN_RESTRICTION;
+ }
+
+ status = openat_pathref_fsp(dirfsp, smb_fname_rel);
+ }
+
+ if (NT_STATUS_IS_OK(status) && (cache_key.data != NULL)) {
+ const char *slash = strchr_m(smb_fname_rel->base_name, '/');
+
+ if (slash == NULL) {
+ DATA_BLOB value = {
+ .data = (uint8_t *)smb_fname_rel->base_name,
+ .length = strlen(smb_fname_rel->base_name) + 1,
+ };
+ memcache_add(
+ NULL, GETREALFILENAME_CACHE, cache_key, value);
+ }
+ }
+
+ TALLOC_FREE(cache_key.data);
+
+ return status;
+}
+
+/*
+ * Split up name_in as sent by the client into a directory pathref fsp
+ * and a relative smb_filename.
+ */
+static const char *previous_slash(const char *name_in, const char *slash)
+{
+ const char *prev = name_in;
+
+ while (true) {
+ const char *next = strchr_m(prev, '/');
+
+ SMB_ASSERT(next != NULL); /* we have at least one slash */
+
+ if (next == slash) {
+ break;
+ }
+
+ prev = next+1;
+ };
+
+ if (prev == name_in) {
+ /* no previous slash */
+ return NULL;
+ }
+
+ return prev;
+}
+
+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) {
+ /* no previous slash */
+ parent = name_in;
+ }
+
+ ret = talloc_asprintf(
+ mem_ctx,
+ "%.*s%s%s",
+ (int)(parent - name_in),
+ name_in,
+ substitute,
+ p_unparsed);
+ return ret;
+}
+
+/*
+ * 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,
+ char **_substitute,
+ size_t *_unparsed)
+{
+ struct smb_filename *smb_dirname = NULL;
+ struct smb_filename *smb_fname_rel = NULL;
+ struct smb_filename *smb_fname = 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;
+
+ if (ucf_flags & UCF_DFS_PATHNAME) {
+ /*
+ * We've been given a raw DFS pathname.
+ */
+ char *pathname = NULL;
+ DBG_DEBUG("Before dfs_filename_convert name_in: %s\n", name_in);
+ status = dfs_filename_convert(mem_ctx,
+ conn,
+ ucf_flags,
+ name_in,
+ &pathname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dfs_filename_convert "
+ "failed for name %s with %s\n",
+ name_in,
+ nt_errstr(status));
+ return status;
+ }
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+ name_in = pathname;
+ DBG_DEBUG("After dfs_filename_convert name_in: %s\n", name_in);
+ }
+
+ 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 };
+ 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 {
+ char *substitute = NULL;
+ size_t unparsed = 0;
+
+ 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_dirfsp_nosymlink(
+ mem_ctx,
+ conn,
+ dirname,
+ 0,
+ &smb_dirname,
+ &unparsed,
+ &substitute);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+
+ size_t name_in_len = strlen(name_in);
+ size_t dirname_len = strlen(dirname);
+
+ SMB_ASSERT(name_in_len >= dirname_len);
+
+ *_substitute = substitute;
+ *_unparsed = unparsed + (name_in_len - dirname_len);
+
+ 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_PATH_NOT_COVERED)) {
+ /* MS-DFS error must propagate back out. */
+ goto fail;
+ }
+
+ 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;
+ }
+
+ /*
+ * 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_case_insensitive(
+ smb_dirname->fsp, smb_fname_rel, ucf_flags);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+
+ char *normalized = NULL;
+
+ if (VALID_STAT(smb_fname_rel->st)) {
+ /*
+ * If we're on an MSDFS share, see if this is
+ * an MSDFS link.
+ */
+ if (lp_host_msdfs() &&
+ lp_msdfs_root(SNUM(conn)) &&
+ S_ISLNK(smb_fname_rel->st.st_ex_mode) &&
+ is_msdfs_link(smb_dirname->fsp, smb_fname_rel))
+ {
+ status = NT_STATUS_PATH_NOT_COVERED;
+ goto fail;
+ }
+
+#if defined(WITH_SMB1SERVER)
+ /*
+ * In SMB1 posix mode, if this is a symlink,
+ * allow access to the name with a NULL smb_fname->fsp.
+ */
+ if (!conn->sconn->using_smb2 &&
+ posix &&
+ S_ISLNK(smb_fname_rel->st.st_ex_mode)) {
+ SMB_ASSERT(smb_fname_rel->fsp == NULL);
+ SMB_ASSERT(streamname == NULL);
+
+ 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;
+ }
+#endif
+ /*
+ * NT_STATUS_OBJECT_NAME_NOT_FOUND is
+ * misleading: The object exists but might be
+ * a symlink pointing outside the share.
+ */
+ goto fail;
+ }
+
+ /*
+ * 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;
+
+ 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)
+{
+ char *substitute = NULL;
+ const char *relative = NULL;
+ size_t unparsed = 0;
+ NTSTATUS status;
+ char *target = NULL;
+ char *abs_target = NULL;
+ char *abs_target_canon = NULL;
+ size_t symlink_redirects = 0;
+ bool in_share;
+
+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,
+ &substitute,
+ &unparsed);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ return status;
+ }
+
+ if (!lp_follow_symlinks(SNUM(conn))) {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+
+ /*
+ * 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, substitute, unparsed);
+ if (target == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_DEBUG("name_in: %s, substitute: %s, unparsed: %zu, target=%s\n",
+ name_in,
+ substitute,
+ unparsed,
+ target);
+
+ if (target[0] == '/') {
+ abs_target = target;
+ } else {
+ abs_target = talloc_asprintf(
+ mem_ctx, "%s/%s", conn->connectpath, target);
+ if (abs_target == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ abs_target_canon = canonicalize_absolute_path(mem_ctx, abs_target);
+ if (abs_target_canon == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_DEBUG("abs_target_canon=%s\n", abs_target_canon);
+
+ in_share = subdir_of(
+ conn->connectpath,
+ strlen(conn->connectpath),
+ abs_target_canon,
+ &relative);
+ if (!in_share) {
+ DBG_DEBUG("wide link to %s\n", abs_target_canon);
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+
+ name_in = talloc_strdup(mem_ctx, relative);
+
+ symlink_redirects += 1;
+
+ goto next;
+}
+
+/*
+ * 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;
+
+ if (dirfsp == dirfsp->conn->cwd_fsp ||
+ ISDOT(dirfsp->fsp_name->base_name) ||
+ atname->base_name[0] == '/')
+ {
+ path = talloc_strdup(mem_ctx, atname->base_name);
+ } else {
+ path = talloc_asprintf(mem_ctx, "%s/%s",
+ dirfsp->fsp_name->base_name,
+ 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;
+}