Proper fix for #6898 - Samba duplicates file content on appending. Pointed out by...
[ira/wip.git] / source3 / smbd / filename.c
index 3eb2d63734a9e1cad5b5d9455ca36b17e44bc776..16e36312bbf78fa472f36ab6e9f6a05f55310086 100644 (file)
 
 #include "includes.h"
 
-static bool scan_directory(connection_struct *conn, const char *path,
-                          char *name, char **found_name);
 static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
                                  connection_struct *conn,
                                  const char *orig_path,
-                                 const char *basepath,
-                                 const char *streamname,
-                                 SMB_STRUCT_STAT *pst,
-                                 char **path);
+                                 struct smb_filename *smb_fname);
 
 /****************************************************************************
  Mangle the 2nd name and check if it is then equal to the first name.
@@ -87,8 +82,8 @@ static NTSTATUS determine_path_error(const char *name,
 
 /****************************************************************************
 This routine is called to convert names from the dos namespace to unix
-namespace. It needs to handle any case conversions, mangling, format
-changes etc.
+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.
@@ -96,53 +91,61 @@ for this service.
 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.
+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 saved_last_component != 0, then the unmodified last component
-of the pathname is returned there. If saved_last_component == 0 then nothing
-is returned there.
+If the UCF_SAVE_LCOMP flag is passed in, then the unmodified last component
+of the pathname is set in smb_filename->original_lcomp.
 
-If last_component_wcard is true then a MS wildcard was detected and
-should be allowed in the last component of the path only.
+If UCF_ALWAYS_ALLOW_WCARD_LCOMP is passed in, then a MS wildcard was detected
+and should be allowed in the last component of the path only.
 
-On exit from unix_convert, if *pst was not null, then the file stat
-struct will be returned 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).
+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).
 ****************************************************************************/
 
 NTSTATUS unix_convert(TALLOC_CTX *ctx,
-                       connection_struct *conn,
-                       const char *orig_path,
-                       bool allow_wcard_last_component,
-                       char **pp_conv_path,
-                       char **pp_saved_last_component,
-                       SMB_STRUCT_STAT *pst)
+                     connection_struct *conn,
+                     const char *orig_path,
+                     struct smb_filename **smb_fname_out,
+                     uint32_t ucf_flags)
 {
-       SMB_STRUCT_STAT st;
+       struct smb_filename *smb_fname = NULL;
        char *start, *end;
        char *dirpath = NULL;
-       char *name = NULL;
        char *stream = NULL;
        bool component_was_mangled = False;
        bool name_has_wildcard = False;
-       NTSTATUS result;
+       bool posix_pathnames = false;
+       bool allow_wcard_last_component =
+           (ucf_flags & UCF_ALWAYS_ALLOW_WCARD_LCOMP);
+       bool save_last_component = ucf_flags & UCF_SAVE_LCOMP;
+       NTSTATUS status;
+       int ret = -1;
 
-       SET_STAT_INVALID(*pst);
-       *pp_conv_path = NULL;
-       if(pp_saved_last_component) {
-               *pp_saved_last_component = NULL;
+       *smb_fname_out = NULL;
+
+       smb_fname = talloc_zero(ctx, struct smb_filename);
+       if (smb_fname == NULL) {
+               return NT_STATUS_NO_MEMORY;
        }
 
        if (conn->printer) {
                /* we don't ever use the filenames on a printer share as a
                        filename - so don't convert them */
-               if (!(*pp_conv_path = talloc_strdup(ctx,orig_path))) {
-                       return NT_STATUS_NO_MEMORY;
+               if (!(smb_fname->base_name = talloc_strdup(smb_fname,
+                                                          orig_path))) {
+                       status = NT_STATUS_NO_MEMORY;
+                       goto err;
                }
-               return NT_STATUS_OK;
+               goto done;
        }
 
        DEBUG(5, ("unix_convert called on file \"%s\"\n", orig_path));
@@ -170,15 +173,16 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
         */
 
        if (!*orig_path) {
-               if (!(name = talloc_strdup(ctx,"."))) {
-                       return NT_STATUS_NO_MEMORY;
+               if (!(smb_fname->base_name = talloc_strdup(smb_fname, "."))) {
+                       status = NT_STATUS_NO_MEMORY;
+                       goto err;
                }
-               if (SMB_VFS_STAT(conn,name,&st) == 0) {
-                       *pst = st;
-               } else {
-                       return map_nt_error_from_unix(errno);
+               if (SMB_VFS_STAT(conn, smb_fname) != 0) {
+                       status = map_nt_error_from_unix(errno);
+                       goto err;
                }
-               DEBUG(5,("conversion finished \"\" -> %s\n",name));
+               DEBUG(5, ("conversion finished \"\" -> %s\n",
+                         smb_fname->base_name));
                goto done;
        }
 
@@ -186,77 +190,95 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                orig_path[1] == '\0')) {
                /* Start of pathname can't be "." only. */
                if (orig_path[1] == '\0' || orig_path[2] == '\0') {
-                       result = NT_STATUS_OBJECT_NAME_INVALID;
+                       status = NT_STATUS_OBJECT_NAME_INVALID;
                } else {
-                       result =determine_path_error(
-                               &orig_path[2], allow_wcard_last_component);
+                       status =determine_path_error(&orig_path[2],
+                           allow_wcard_last_component);
                }
-               return result;
+               goto err;
+       }
+
+       /* Start with the full orig_path as given by the caller. */
+       if (!(smb_fname->base_name = talloc_strdup(smb_fname, orig_path))) {
+               DEBUG(0, ("talloc_strdup failed\n"));
+               status = NT_STATUS_NO_MEMORY;
+               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.
+        */
+
+       if (conn->case_sensitive && !conn->case_preserve &&
+                       !conn->short_case_preserve) {
+               strnorm(smb_fname->base_name, lp_defaultcase(SNUM(conn)));
        }
 
        /*
         * Ensure saved_last_component is valid even if file exists.
         */
 
-       if(pp_saved_last_component) {
-               end = strrchr_m(orig_path, '/');
+       if(save_last_component) {
+               end = strrchr_m(smb_fname->base_name, '/');
                if (end) {
-                       *pp_saved_last_component = talloc_strdup(ctx, end + 1);
+                       smb_fname->original_lcomp = talloc_strdup(smb_fname,
+                                                                 end + 1);
                } else {
-                       *pp_saved_last_component = talloc_strdup(ctx,
-                                                       orig_path);
+                       smb_fname->original_lcomp =
+                           talloc_strdup(smb_fname, smb_fname->base_name);
                }
-               if (conn->case_sensitive && !conn->case_preserve &&
-                   !conn->short_case_preserve) {
-                       strnorm(*pp_saved_last_component,
-                               lp_defaultcase(SNUM(conn)));
+               if (smb_fname->original_lcomp == NULL) {
+                       status = NT_STATUS_NO_MEMORY;
+                       goto err;
                }
        }
 
-       if (!(name = talloc_strdup(ctx, orig_path))) {
-               DEBUG(0, ("talloc_strdup failed\n"));
-               return NT_STATUS_NO_MEMORY;
-       }
+       posix_pathnames = (lp_posix_pathnames() ||
+                               (ucf_flags & UCF_POSIX_PATHNAMES));
 
-       if (!lp_posix_pathnames()) {
-               stream = strchr_m(name, ':');
+       /*
+        * Strip off the stream, and add it back when we're done with the
+        * base_name.
+        */
+       if (!posix_pathnames) {
+               stream = strchr_m(smb_fname->base_name, ':');
 
                if (stream != NULL) {
-                       char *tmp = talloc_strdup(ctx, stream);
+                       char *tmp = talloc_strdup(smb_fname, stream);
                        if (tmp == NULL) {
-                               TALLOC_FREE(name);
-                               return NT_STATUS_NO_MEMORY;
+                               status = NT_STATUS_NO_MEMORY;
+                               goto err;
                        }
+                       /*
+                        * Since this is actually pointing into
+                        * smb_fname->base_name this truncates base_name.
+                        */
                        *stream = '\0';
                        stream = tmp;
                }
        }
 
-       /*
-        * 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.
-        */
-
-       if (conn->case_sensitive && !conn->case_preserve &&
-                       !conn->short_case_preserve) {
-               strnorm(name, lp_defaultcase(SNUM(conn)));
-       }
-
-       start = name;
+       start = smb_fname->base_name;
 
-       /* If we're providing case insentive semantics or
+       /*
+        * If we're providing case insentive semantics or
         * the underlying filesystem is case insensitive,
         * then a case-normalized hit in the stat-cache is
         * authoratitive. JRA.
+        *
+        * Note: We're only checking base_name.  The stream_name will be
+        * added and verified in build_stream_path().
         */
 
-       if((!conn->case_sensitive || !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) &&
-                       stat_cache_lookup(conn, &name, &dirpath, &start, &st)) {
-               *pst = st;
+       if((!conn->case_sensitive || !(conn->fs_capabilities &
+                                      FILE_CASE_SENSITIVE_SEARCH)) &&
+           stat_cache_lookup(conn, &smb_fname->base_name, &dirpath, &start,
+                             &smb_fname->st)) {
                goto done;
        }
 
@@ -267,37 +289,46 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
 
        if ((dirpath == NULL) && (!(dirpath = talloc_strdup(ctx,"")))) {
                DEBUG(0, ("talloc_strdup failed\n"));
-               TALLOC_FREE(name);
-               return NT_STATUS_NO_MEMORY;
+               status = NT_STATUS_NO_MEMORY;
+               goto err;
        }
 
        /*
-        * stat the name - if it exists then we are all done!
+        * stat the name - if it exists then we can add the stream back (if
+        * there was one) and be done!
         */
 
-       if (SMB_VFS_STAT(conn,name,&st) == 0) {
+       if (posix_pathnames) {
+               ret = SMB_VFS_LSTAT(conn, smb_fname);
+       } else {
+               ret = SMB_VFS_STAT(conn, smb_fname);
+       }
+
+       if (ret == 0) {
                /* Ensure we catch all names with in "/."
                   this is disallowed under Windows. */
-               const char *p = strstr(name, "/."); /* mb safe. */
+               const char *p = strstr(smb_fname->base_name, "/."); /*mb safe*/
                if (p) {
                        if (p[2] == '/') {
                                /* Error code within a pathname. */
-                               result = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+                               status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
                                goto fail;
                        } else if (p[2] == '\0') {
                                /* Error code at the end of a pathname. */
-                               result = NT_STATUS_OBJECT_NAME_INVALID;
+                               status = NT_STATUS_OBJECT_NAME_INVALID;
                                goto fail;
                        }
                }
-               stat_cache_add(orig_path, name, conn->case_sensitive);
-               DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
-               *pst = st;
+               /* Add the path (not including the stream) to the cache. */
+               stat_cache_add(orig_path, smb_fname->base_name,
+                              conn->case_sensitive);
+               DEBUG(5,("conversion of base_name finished %s -> %s\n",
+                        orig_path, smb_fname->base_name));
                goto done;
        }
 
        DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n",
-                               name, dirpath, start));
+                smb_fname->base_name, dirpath, start));
 
        /*
         * A special case - if we don't have any mangling chars and are case
@@ -305,8 +336,9 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
         * won't help.
         */
 
-       if ((conn->case_sensitive || !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) &&
-                       !mangle_is_mangled(name, conn->params)) {
+       if ((conn->case_sensitive || !(conn->fs_capabilities &
+                                      FILE_CASE_SENSITIVE_SEARCH)) &&
+           !mangle_is_mangled(smb_fname->base_name, conn->params)) {
                goto done;
        }
 
@@ -343,13 +375,14 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                        *end = 0;
                }
 
-               if (pp_saved_last_component) {
-                       TALLOC_FREE(*pp_saved_last_component);
-                       *pp_saved_last_component = talloc_strdup(ctx,
+               if (save_last_component) {
+                       TALLOC_FREE(smb_fname->original_lcomp);
+                       smb_fname->original_lcomp = talloc_strdup(smb_fname,
                                                        end ? end + 1 : start);
-                       if (!*pp_saved_last_component) {
+                       if (!smb_fname->original_lcomp) {
                                DEBUG(0, ("talloc failed\n"));
-                               return NT_STATUS_NO_MEMORY;
+                               status = NT_STATUS_NO_MEMORY;
+                               goto err;
                        }
                }
 
@@ -358,9 +391,9 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                if (ISDOT(start)) {
                        if (!end)  {
                                /* Error code at the end of a pathname. */
-                               result = NT_STATUS_OBJECT_NAME_INVALID;
+                               status = NT_STATUS_OBJECT_NAME_INVALID;
                        } else {
-                               result = determine_path_error(end+1,
+                               status = determine_path_error(end+1,
                                                allow_wcard_last_component);
                        }
                        goto fail;
@@ -373,13 +406,13 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
 
                /* Wildcard not valid anywhere. */
                if (name_has_wildcard && !allow_wcard_last_component) {
-                       result = NT_STATUS_OBJECT_NAME_INVALID;
+                       status = NT_STATUS_OBJECT_NAME_INVALID;
                        goto fail;
                }
 
                /* Wildcards never valid within a pathname. */
                if (name_has_wildcard && end) {
-                       result = NT_STATUS_OBJECT_NAME_INVALID;
+                       status = NT_STATUS_OBJECT_NAME_INVALID;
                        goto fail;
                }
 
@@ -387,12 +420,18 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                 * Check if the name exists up to this point.
                 */
 
-               if (SMB_VFS_STAT(conn,name, &st) == 0) {
+               if (posix_pathnames) {
+                       ret = SMB_VFS_LSTAT(conn, smb_fname);
+               } else {
+                       ret = SMB_VFS_STAT(conn, 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 (end && !(st.st_mode & S_IFDIR)) {
+                       if (end && !S_ISDIR(smb_fname->st.st_ex_mode)) {
                                /*
                                 * An intermediate part of the name isn't
                                 * a directory.
@@ -407,25 +446,15 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                 * applications depend on the difference between
                                 * these two errors.
                                 */
-                               result = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+                               status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
                                goto fail;
                        }
 
-                       if (!end) {
-                               /*
-                                * We just scanned for, and found the end of
-                                * the path. We must return the valid stat
-                                * struct. JRA.
-                                */
-
-                               *pst = st;
-                       }
-
                } else {
                        char *found_name = NULL;
 
                        /* Stat failed - ensure we don't use it. */
-                       SET_STAT_INVALID(st);
+                       SET_STAT_INVALID(smb_fname->st);
 
                        /*
                         * Reset errno so we can detect
@@ -438,8 +467,9 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                         */
 
                        if (name_has_wildcard ||
-                           !scan_directory(conn, dirpath,
-                                   start, &found_name)) {
+                           (get_real_filename(conn, dirpath, start,
+                                              talloc_tos(),
+                                              &found_name) == -1)) {
                                char *unmangled;
 
                                if (end) {
@@ -470,18 +500,24 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                        if (errno == ENOENT ||
                                                        errno == ENOTDIR ||
                                                        errno == ELOOP) {
-                                               result =
+                                               status =
                                                NT_STATUS_OBJECT_PATH_NOT_FOUND;
                                        }
                                        else {
-                                               result =
+                                               status =
                                                map_nt_error_from_unix(errno);
                                        }
                                        goto fail;
                                }
 
-                               /* ENOENT is the only valid error here. */
-                               if ((errno != 0) && (errno != ENOENT)) {
+                               /*
+                                * ENOENT/EACCESS are the only valid errors
+                                * here. EACCESS needs handling here for
+                                * "dropboxes", i.e. directories where users
+                                * can only put stuff with permission -wx.
+                                */
+                               if ((errno != 0) && (errno != ENOENT)
+                                   && (errno != EACCES)) {
                                        /*
                                         * ENOTDIR and ELOOP both map to
                                         * NT_STATUS_OBJECT_PATH_NOT_FOUND
@@ -489,11 +525,10 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                         */
                                        if (errno == ENOTDIR ||
                                                        errno == ELOOP) {
-                                               result =
+                                               status =
                                                NT_STATUS_OBJECT_PATH_NOT_FOUND;
-                                       }
-                                       else {
-                                               result =
+                                       } else {
+                                               status =
                                                map_nt_error_from_unix(errno);
                                        }
                                        goto fail;
@@ -525,12 +560,13 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                                        &unmangled,
                                                        conn->params)) {
                                        char *tmp;
-                                       size_t start_ofs = start - name;
+                                       size_t start_ofs =
+                                           start - smb_fname->base_name;
 
                                        if (*dirpath != '\0') {
-                                               tmp = talloc_asprintf(ctx,
-                                                       "%s/%s", dirpath,
-                                                       unmangled);
+                                               tmp = talloc_asprintf(
+                                                       smb_fname, "%s/%s",
+                                                       dirpath, unmangled);
                                                TALLOC_FREE(unmangled);
                                        }
                                        else {
@@ -538,11 +574,13 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                        }
                                        if (tmp == NULL) {
                                                DEBUG(0, ("talloc failed\n"));
-                                               return NT_STATUS_NO_MEMORY;
+                                               status = NT_STATUS_NO_MEMORY;
+                                               goto err;
                                        }
-                                       TALLOC_FREE(name);
-                                       name = tmp;
-                                       start = name + start_ofs;
+                                       TALLOC_FREE(smb_fname->base_name);
+                                       smb_fname->base_name = tmp;
+                                       start =
+                                           smb_fname->base_name + start_ofs;
                                        end = start + strlen(start);
                                }
 
@@ -557,46 +595,50 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                         */
                        if (end) {
                                char *tmp;
-                               size_t start_ofs = start - name;
+                               size_t start_ofs =
+                                   start - smb_fname->base_name;
 
                                if (*dirpath != '\0') {
-                                       tmp = talloc_asprintf(ctx,
+                                       tmp = talloc_asprintf(smb_fname,
                                                "%s/%s/%s", dirpath,
                                                found_name, end+1);
                                }
                                else {
-                                       tmp = talloc_asprintf(ctx,
+                                       tmp = talloc_asprintf(smb_fname,
                                                "%s/%s", found_name,
                                                end+1);
                                }
                                if (tmp == NULL) {
                                        DEBUG(0, ("talloc_asprintf failed\n"));
-                                       return NT_STATUS_NO_MEMORY;
+                                       status = NT_STATUS_NO_MEMORY;
+                                       goto err;
                                }
-                               TALLOC_FREE(name);
-                               name = tmp;
-                               start = name + start_ofs;
+                               TALLOC_FREE(smb_fname->base_name);
+                               smb_fname->base_name = tmp;
+                               start = smb_fname->base_name + start_ofs;
                                end = start + strlen(found_name);
                                *end = '\0';
                        } else {
                                char *tmp;
-                               size_t start_ofs = start - name;
+                               size_t start_ofs =
+                                   start - smb_fname->base_name;
 
                                if (*dirpath != '\0') {
-                                       tmp = talloc_asprintf(ctx,
+                                       tmp = talloc_asprintf(smb_fname,
                                                "%s/%s", dirpath,
                                                found_name);
                                } else {
-                                       tmp = talloc_strdup(ctx,
+                                       tmp = talloc_strdup(smb_fname,
                                                found_name);
                                }
                                if (tmp == NULL) {
                                        DEBUG(0, ("talloc failed\n"));
-                                       return NT_STATUS_NO_MEMORY;
+                                       status = NT_STATUS_NO_MEMORY;
+                                       goto err;
                                }
-                               TALLOC_FREE(name);
-                               name = tmp;
-                               start = name + start_ofs;
+                               TALLOC_FREE(smb_fname->base_name);
+                               smb_fname->base_name = tmp;
+                               start = smb_fname->base_name + start_ofs;
 
                                /*
                                 * We just scanned for, and found the end of
@@ -604,10 +646,14 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                 * if it exists. JRA.
                                 */
 
-                               if (SMB_VFS_STAT(conn,name, &st) == 0) {
-                                       *pst = st;
+                               if (posix_pathnames) {
+                                       ret = SMB_VFS_LSTAT(conn, smb_fname);
                                } else {
-                                       SET_STAT_INVALID(st);
+                                       ret = SMB_VFS_STAT(conn, smb_fname);
+                               }
+
+                               if (ret != 0) {
+                                       SET_STAT_INVALID(smb_fname->st);
                                }
                        }
 
@@ -620,12 +666,13 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                 * We should never provide different behaviors
                 * depending on DEVELOPER!!!
                 */
-               if (VALID_STAT(st)) {
+               if (VALID_STAT(smb_fname->st)) {
                        bool delete_pending;
-                       get_file_infos(vfs_file_id_from_sbuf(conn, &st),
+                       get_file_infos(vfs_file_id_from_sbuf(conn,
+                                                            &smb_fname->st),
                                       &delete_pending, NULL);
                        if (delete_pending) {
-                               result = NT_STATUS_DELETE_PENDING;
+                               status = NT_STATUS_DELETE_PENDING;
                                goto fail;
                        }
                }
@@ -640,7 +687,8 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                        "%s/%s", dirpath, start);
                        if (!tmp) {
                                DEBUG(0, ("talloc_asprintf failed\n"));
-                               return NT_STATUS_NO_MEMORY;
+                               status = NT_STATUS_NO_MEMORY;
+                               goto err;
                        }
                        TALLOC_FREE(dirpath);
                        dirpath = tmp;
@@ -649,15 +697,15 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                        TALLOC_FREE(dirpath);
                        if (!(dirpath = talloc_strdup(ctx,start))) {
                                DEBUG(0, ("talloc_strdup failed\n"));
-                               return NT_STATUS_NO_MEMORY;
+                               status = NT_STATUS_NO_MEMORY;
+                               goto err;
                        }
                }
 
                /*
-                * Don't cache a name with mangled or wildcard components
-                * as this can change the size.
+                * Cache the dirpath thus far. Don't cache a name with mangled
+                * or wildcard components as this can change the size.
                 */
-
                if(!component_was_mangled && !name_has_wildcard) {
                        stat_cache_add(orig_path, dirpath,
                                        conn->case_sensitive);
@@ -672,53 +720,56 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
        }
 
        /*
-        * Don't cache a name with mangled or wildcard components
-        * as this can change the size.
+        * Cache the full path. Don't cache a name with mangled or wildcard
+        * components as this can change the size.
         */
 
        if(!component_was_mangled && !name_has_wildcard) {
-               stat_cache_add(orig_path, name, conn->case_sensitive);
+               stat_cache_add(orig_path, smb_fname->base_name,
+                              conn->case_sensitive);
        }
 
        /*
         * The name has been resolved.
         */
 
-       DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
+       DEBUG(5,("conversion finished %s -> %s\n", orig_path,
+                smb_fname->base_name));
 
  done:
+       /* Add back the stream if one was stripped off originally. */
        if (stream != NULL) {
-               char *tmp = NULL;
+               smb_fname->stream_name = stream;
 
-               result = build_stream_path(ctx, conn, orig_path, name, stream,
-                                          pst, &tmp);
-               if (!NT_STATUS_IS_OK(result)) {
+               /* Check path now that the base_name has been converted. */
+               status = build_stream_path(ctx, conn, orig_path, smb_fname);
+               if (!NT_STATUS_IS_OK(status)) {
                        goto fail;
                }
-
-               DEBUG(10, ("build_stream_path returned %s\n", tmp));
-
-               TALLOC_FREE(name);
-               name = tmp;
        }
-       *pp_conv_path = name;
        TALLOC_FREE(dirpath);
+       *smb_fname_out = smb_fname;
        return NT_STATUS_OK;
  fail:
        DEBUG(10, ("dirpath = [%s] start = [%s]\n", dirpath, start));
        if (*dirpath != '\0') {
-               *pp_conv_path = talloc_asprintf(ctx,
-                               "%s/%s", dirpath, start);
+               smb_fname->base_name = talloc_asprintf(smb_fname, "%s/%s",
+                                                      dirpath, start);
        } else {
-               *pp_conv_path = talloc_strdup(ctx, start);
+               smb_fname->base_name = talloc_strdup(smb_fname, start);
        }
-       if (!*pp_conv_path) {
+       if (!smb_fname->base_name) {
                DEBUG(0, ("talloc_asprintf failed\n"));
-               return NT_STATUS_NO_MEMORY;
+               status = NT_STATUS_NO_MEMORY;
+               goto err;
        }
-       TALLOC_FREE(name);
+
+       *smb_fname_out = smb_fname;
        TALLOC_FREE(dirpath);
-       return result;
+       return status;
+ err:
+       TALLOC_FREE(smb_fname);
+       return status;
 }
 
 /****************************************************************************
@@ -773,17 +824,16 @@ static bool fname_equal(const char *name1, const char *name2,
  If the name looks like a mangled name then try via the mangling functions
 ****************************************************************************/
 
-static bool scan_directory(connection_struct *conn, const char *path,
-                          char *name, char **found_name)
+static int 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_Dir *cur_dir;
-       const char *dname;
-       bool mangled;
+       const char *dname = NULL;
+       char *talloced = NULL;
        char *unmangled_name = NULL;
        long curpos;
-       TALLOC_CTX *ctx = talloc_tos();
-
-       mangled = mangle_is_mangled(name, conn->params);
 
        /* handle null paths */
        if ((path == NULL) || (*path == 0)) {
@@ -796,7 +846,7 @@ static bool scan_directory(connection_struct *conn, const char *path,
         */
        if (!mangled && !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) {
                errno = ENOENT;
-               return False;
+               return -1;
        }
 
        /*
@@ -815,10 +865,9 @@ static bool scan_directory(connection_struct *conn, const char *path,
         */
 
        if (mangled && !conn->case_sensitive) {
-               mangled = !mangle_lookup_name_from_8_3(ctx,
-                                               name,
-                                               &unmangled_name,
-                                               conn->params);
+               mangled = !mangle_lookup_name_from_8_3(talloc_tos(), name,
+                                                      &unmangled_name,
+                                                      conn->params);
                if (!mangled) {
                        /* Name is now unmangled. */
                        name = unmangled_name;
@@ -829,15 +878,16 @@ static bool scan_directory(connection_struct *conn, const char *path,
        if (!(cur_dir = OpenDir(talloc_tos(), conn, path, NULL, 0))) {
                DEBUG(3,("scan dir didn't open dir [%s]\n",path));
                TALLOC_FREE(unmangled_name);
-               return(False);
+               return -1;
        }
 
        /* now scan for matching names */
        curpos = 0;
-       while ((dname = ReadDirName(cur_dir, &curpos))) {
+       while ((dname = ReadDirName(cur_dir, &curpos, NULL, &talloced))) {
 
                /* Is it dot or dot dot. */
                if (ISDOT(dname) || ISDOTDOT(dname)) {
+                       TALLOC_FREE(talloced);
                        continue;
                }
 
@@ -855,60 +905,87 @@ static bool scan_directory(connection_struct *conn, const char *path,
                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(ctx,dname);
+                       *found_name = talloc_strdup(mem_ctx, dname);
                        TALLOC_FREE(unmangled_name);
                        TALLOC_FREE(cur_dir);
                        if (!*found_name) {
                                errno = ENOMEM;
-                               return False;
+                               TALLOC_FREE(talloced);
+                               return -1;
                        }
-                       return(True);
+                       TALLOC_FREE(talloced);
+                       return 0;
                }
+               TALLOC_FREE(talloced);
        }
 
        TALLOC_FREE(unmangled_name);
        TALLOC_FREE(cur_dir);
        errno = ENOENT;
-       return False;
+       return -1;
+}
+
+/****************************************************************************
+ Wrapper around the vfs get_real_filename and the full directory scan
+ fallback.
+****************************************************************************/
+
+int get_real_filename(connection_struct *conn, const char *path,
+                     const char *name, TALLOC_CTX *mem_ctx,
+                     char **found_name)
+{
+       int ret;
+       bool mangled;
+
+       mangled = mangle_is_mangled(name, conn->params);
+
+       if (mangled) {
+               return get_real_filename_full_scan(conn, path, name, mangled,
+                                                  mem_ctx, found_name);
+       }
+
+       /* Try the vfs first to take advantage of case-insensitive stat. */
+       ret = SMB_VFS_GET_REAL_FILENAME(conn, path, 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 (ret == 0 || (ret == -1 && errno != EOPNOTSUPP)) {
+               return ret;
+       }
+
+       return get_real_filename_full_scan(conn, path, name, mangled, mem_ctx,
+                                          found_name);
 }
 
 static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
                                  connection_struct *conn,
                                  const char *orig_path,
-                                 const char *basepath,
-                                 const char *streamname,
-                                 SMB_STRUCT_STAT *pst,
-                                 char **path)
+                                 struct smb_filename *smb_fname)
 {
-       SMB_STRUCT_STAT st;
-       char *result = NULL;
        NTSTATUS status;
        unsigned int i, num_streams;
        struct stream_struct *streams = NULL;
 
-       result = talloc_asprintf(mem_ctx, "%s%s", basepath, streamname);
-       if (result == NULL) {
-               return NT_STATUS_NO_MEMORY;
-       }
-
-       if (SMB_VFS_STAT(conn, result, &st) == 0) {
-               *pst = st;
-               *path = result;
+       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);
-               DEBUG(10, ("vfs_stat failed: %s\n", nt_errstr(status)));
                goto fail;
        }
 
-       status = SMB_VFS_STREAMINFO(conn, NULL, basepath, mem_ctx,
+       /* Fall back to a case-insensitive scan of all streams on the file. */
+       status = SMB_VFS_STREAMINFO(conn, NULL, smb_fname->base_name, mem_ctx,
                                    &num_streams, &streams);
 
        if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
-               SET_STAT_INVALID(*pst);
-               *path = result;
+               SET_STAT_INVALID(smb_fname->st);
                return NT_STATUS_OK;
        }
 
@@ -919,8 +996,8 @@ static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
 
        for (i=0; i<num_streams; i++) {
                DEBUG(10, ("comparing [%s] and [%s]: ",
-                          streamname, streams[i].name));
-               if (fname_equal(streamname, streams[i].name,
+                          smb_fname->stream_name, streams[i].name));
+               if (fname_equal(smb_fname->stream_name, streams[i].name,
                                conn->case_sensitive)) {
                        DEBUGADD(10, ("equal\n"));
                        break;
@@ -928,33 +1005,118 @@ static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
                DEBUGADD(10, ("not equal\n"));
        }
 
+       /* Couldn't find the stream. */
        if (i == num_streams) {
-               SET_STAT_INVALID(*pst);
-               *path = result;
+               SET_STAT_INVALID(smb_fname->st);
                TALLOC_FREE(streams);
                return NT_STATUS_OK;
        }
 
-       TALLOC_FREE(result);
+       DEBUG(10, ("case insensitive stream. requested: %s, actual: %s\n",
+               smb_fname->stream_name, streams[i].name));
+
 
-       result = talloc_asprintf(mem_ctx, "%s%s", basepath, streams[i].name);
-       if (result == NULL) {
+       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(*pst);
+       SET_STAT_INVALID(smb_fname->st);
 
-       if (SMB_VFS_STAT(conn, result, pst) == 0) {
-               stat_cache_add(orig_path, result, conn->case_sensitive);
+       if (SMB_VFS_STAT(conn, smb_fname) == 0) {
+               DEBUG(10, ("'%s' exists\n", smb_fname_str_dbg(smb_fname)));
        }
-
-       *path = result;
-       TALLOC_FREE(streams);
-       return NT_STATUS_OK;
-
+       status = NT_STATUS_OK;
  fail:
-       TALLOC_FREE(result);
        TALLOC_FREE(streams);
        return status;
 }
+
+/**
+ * Go through all the steps to validate a filename.
+ *
+ * @param ctx          talloc_ctx to allocate memory with.
+ * @param conn         connection struct for vfs calls.
+ * @param dfs_path     Whether this path requires dfs resolution.
+ * @param name_in      The unconverted name.
+ * @param ucf_flags    flags to pass through to unix_convert().
+ *                     UCF_ALWAYS_ALLOW_WCARD_LCOMP will be OR'd in if
+ *                     p_cont_wcard != NULL and is true and
+ *                     UCF_COND_ALLOW_WCARD_LCOMP.
+ * @param p_cont_wcard If not NULL, will be set to true if the dfs path
+ *                     resolution detects a wildcard.
+ * @param pp_smb_fname The final converted name will be allocated if the
+ *                     return is NT_STATUS_OK.
+ *
+ * @return NT_STATUS_OK if all operations completed succesfully, appropriate
+ *        error otherwise.
+ */
+NTSTATUS filename_convert(TALLOC_CTX *ctx,
+                               connection_struct *conn,
+                               bool dfs_path,
+                               const char *name_in,
+                               uint32_t ucf_flags,
+                               bool *ppath_contains_wcard,
+                               struct smb_filename **pp_smb_fname)
+{
+       NTSTATUS status;
+       char *fname = NULL;
+
+       *pp_smb_fname = NULL;
+
+       status = resolve_dfspath_wcard(ctx, conn,
+                               dfs_path,
+                               name_in,
+                               &fname,
+                               ppath_contains_wcard);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(10,("filename_convert: resolve_dfspath failed "
+                       "for name %s with %s\n",
+                       name_in,
+                       nt_errstr(status) ));
+               return status;
+       }
+
+       if (is_fake_file_path(name_in)) {
+               SMB_STRUCT_STAT st;
+               ZERO_STRUCT(st);
+               st.st_ex_nlink = 1;
+               status = create_synthetic_smb_fname_split(ctx,
+                                                         name_in,
+                                                         &st,
+                                                         pp_smb_fname);
+               return status;
+       }
+
+       /*
+        * If the caller conditionally allows wildcard lookups, only add the
+        * always allow if the path actually does contain a wildcard.
+        */
+       if (ucf_flags & UCF_COND_ALLOW_WCARD_LCOMP &&
+           ppath_contains_wcard != NULL && *ppath_contains_wcard) {
+               ucf_flags |= UCF_ALWAYS_ALLOW_WCARD_LCOMP;
+       }
+
+       status = unix_convert(ctx, conn, fname, pp_smb_fname, ucf_flags);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(10,("filename_convert: unix_convert failed "
+                       "for name %s with %s\n",
+                       fname,
+                       nt_errstr(status) ));
+               return status;
+       }
+
+       status = check_name(conn, (*pp_smb_fname)->base_name);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(3,("filename_convert: check_name failed "
+                       "for name %s with %s\n",
+                       smb_fname_str_dbg(*pp_smb_fname),
+                       nt_errstr(status) ));
+               TALLOC_FREE(*pp_smb_fname);
+               return status;
+       }
+
+       return status;
+}