s3: smbd: fix a crash in unix_convert()
[metze/samba/wip.git] / source3 / smbd / filename.c
index ee97388da28c925e1b66b923765308c9734fcf0e..48814a5790258a3bedd304131656b9a2fe0dfe6a 100644 (file)
  */
 
 #include "includes.h"
+#include "system/filesys.h"
+#include "fake_file.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
 
 static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
                                  connection_struct *conn,
-                                 const char *orig_path,
                                  struct smb_filename *smb_fname);
 
 /****************************************************************************
@@ -80,6 +83,109 @@ static NTSTATUS determine_path_error(const char *name,
        }
 }
 
+static NTSTATUS check_for_dot_component(const struct smb_filename *smb_fname)
+{
+       /* Ensure we catch all names with in "/."
+          this is disallowed under Windows and
+          in POSIX they've already been removed. */
+       const char *p = strstr(smb_fname->base_name, "/."); /*mb safe*/
+       if (p) {
+               if (p[2] == '/') {
+                       /* Error code within a pathname. */
+                       return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+               } else if (p[2] == '\0') {
+                       /* Error code at the end of a pathname. */
+                       return NT_STATUS_OBJECT_NAME_INVALID;
+               }
+       }
+       return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Optimization for common case where the missing part
+ is in the last component and the client already
+ sent the correct case.
+ Returns NT_STATUS_OK to mean continue the tree walk
+ (possibly with modified start pointer).
+ Any other NT_STATUS_XXX error means terminate the path
+ lookup here.
+****************************************************************************/
+
+static NTSTATUS check_parent_exists(TALLOC_CTX *ctx,
+                               connection_struct *conn,
+                               bool posix_pathnames,
+                               const struct smb_filename *smb_fname,
+                               char **pp_dirpath,
+                               char **pp_start)
+{
+       struct smb_filename parent_fname;
+       const char *last_component = NULL;
+       NTSTATUS status;
+       int ret;
+
+       ZERO_STRUCT(parent_fname);
+       if (!parent_dirname(ctx, smb_fname->base_name,
+                               &parent_fname.base_name,
+                               &last_component)) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       /*
+        * If there was no parent component in
+        * smb_fname->base_name of the parent name
+        * contained a wildcard then don't do this
+        * optimization.
+        */
+       if ((smb_fname->base_name == last_component) ||
+                       ms_has_wild(parent_fname.base_name)) {
+               return NT_STATUS_OK;
+       }
+
+       if (posix_pathnames) {
+               ret = SMB_VFS_LSTAT(conn, &parent_fname);
+       } else {
+               ret = SMB_VFS_STAT(conn, &parent_fname);
+       }
+
+       /* If the parent stat failed, just continue
+          with the normal tree walk. */
+
+       if (ret == -1) {
+               return NT_STATUS_OK;
+       }
+
+       status = check_for_dot_component(&parent_fname);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       /* Parent exists - set "start" to be the
+        * last component to shorten the tree walk. */
+
+       /*
+        * Safe to use discard_const_p
+        * here as last_component points
+        * into our smb_fname->base_name.
+        */
+       *pp_start = discard_const_p(char, last_component);
+
+       /* Update dirpath. */
+       TALLOC_FREE(*pp_dirpath);
+       *pp_dirpath = talloc_strdup(ctx, parent_fname.base_name);
+       if (!*pp_dirpath) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       DEBUG(5,("check_parent_exists: name "
+               "= %s, dirpath = %s, "
+               "start = %s\n",
+               smb_fname->base_name,
+               *pp_dirpath,
+               *pp_start));
+
+       return NT_STATUS_OK;
+}
+
 /****************************************************************************
 This routine is called to convert names from the dos namespace to unix
 namespace. It needs to handle any case conversions, mangling, format changes,
@@ -216,7 +322,11 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
 
        if (conn->case_sensitive && !conn->case_preserve &&
                        !conn->short_case_preserve) {
-               strnorm(smb_fname->base_name, lp_defaultcase(SNUM(conn)));
+               if (!strnorm(smb_fname->base_name, lp_default_case(SNUM(conn)))) {
+                       DEBUG(0, ("strnorm %s failed\n", smb_fname->base_name));
+                       status = NT_STATUS_INVALID_PARAMETER;
+                       goto err;
+               }
        }
 
        /*
@@ -238,7 +348,8 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                }
        }
 
-       posix_pathnames = lp_posix_pathnames();
+       posix_pathnames = (lp_posix_pathnames() ||
+                               (ucf_flags & UCF_POSIX_PATHNAMES));
 
        /*
         * Strip off the stream, and add it back when we're done with the
@@ -259,13 +370,36 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                         */
                        *stream = '\0';
                        stream = tmp;
+
+                       if (smb_fname->base_name[0] == '\0') {
+                               /*
+                                * orig_name was just a stream name.
+                                * This is a stream on the root of
+                                * the share. Replace base_name with
+                                * a "."
+                                */
+                               smb_fname->base_name =
+                                       talloc_strdup(smb_fname, ".");
+                               if (smb_fname->base_name == NULL) {
+                                       status = NT_STATUS_NO_MEMORY;
+                                       goto err;
+                               }
+                               if (SMB_VFS_STAT(conn, smb_fname) != 0) {
+                                       status = map_nt_error_from_unix(errno);
+                                       goto err;
+                               }
+                               DEBUG(5, ("conversion finished %s -> %s\n",
+                                       orig_path,
+                                       smb_fname->base_name));
+                               goto done;
+                       }
                }
        }
 
        start = smb_fname->base_name;
 
        /*
-        * If we're providing case insentive semantics or
+        * If we're providing case insensitive semantics or
         * the underlying filesystem is case insensitive,
         * then a case-normalized hit in the stat-cache is
         * authoratitive. JRA.
@@ -276,14 +410,14 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
 
        if((!conn->case_sensitive || !(conn->fs_capabilities &
                                       FILE_CASE_SENSITIVE_SEARCH)) &&
-           stat_cache_lookup(conn, &smb_fname->base_name, &dirpath, &start,
+           stat_cache_lookup(conn, posix_pathnames, &smb_fname->base_name, &dirpath, &start,
                              &smb_fname->st)) {
                goto done;
        }
 
        /*
         * Make sure "dirpath" is an allocated string, we use this for
-        * building the directories with asprintf and free it.
+        * building the directories with talloc_asprintf and free it.
         */
 
        if ((dirpath == NULL) && (!(dirpath = talloc_strdup(ctx,"")))) {
@@ -293,52 +427,147 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
        }
 
        /*
-        * stat the name - if it exists then we can add the stream back (if
-        * there was one) and be done!
+        * If we have a wildcard we must walk the path to
+        * find where the error is, even if case sensitive
+        * is true.
         */
 
-       if (posix_pathnames) {
-               ret = SMB_VFS_LSTAT(conn, smb_fname);
-       } else {
-               ret = SMB_VFS_STAT(conn, smb_fname);
+       name_has_wildcard = ms_has_wild(smb_fname->base_name);
+       if (name_has_wildcard && !allow_wcard_last_component) {
+               /* Wildcard not valid anywhere. */
+               status = NT_STATUS_OBJECT_NAME_INVALID;
+               goto fail;
        }
 
-       if (ret == 0) {
-               /* Ensure we catch all names with in "/."
-                  this is disallowed under Windows. */
-               const char *p = strstr(smb_fname->base_name, "/."); /*mb safe*/
-               if (p) {
-                       if (p[2] == '/') {
-                               /* Error code within a pathname. */
-                               status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+       DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n",
+                smb_fname->base_name, dirpath, start));
+
+       if (!name_has_wildcard) {
+               /*
+                * stat the name - if it exists then we can add the stream back (if
+                * there was one) and be done!
+                */
+
+               if (posix_pathnames) {
+                       ret = SMB_VFS_LSTAT(conn, smb_fname);
+               } else {
+                       ret = SMB_VFS_STAT(conn, smb_fname);
+               }
+
+               if (ret == 0) {
+                       status = check_for_dot_component(smb_fname);
+                       if (!NT_STATUS_IS_OK(status)) {
                                goto fail;
-                       } else if (p[2] == '\0') {
-                               /* Error code at the end of a pathname. */
-                               status = NT_STATUS_OBJECT_NAME_INVALID;
+                       }
+                       /* 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;
+               }
+
+               /* Stat failed - ensure we don't use it. */
+               SET_STAT_INVALID(smb_fname->st);
+
+               if (errno == ENOENT) {
+                       /* Optimization when creating a new file - only
+                          the last component doesn't exist.
+                          NOTE : check_parent_exists() doesn't preserve errno.
+                       */
+                       int saved_errno = errno;
+                       status = check_parent_exists(ctx,
+                                               conn,
+                                               posix_pathnames,
+                                               smb_fname,
+                                               &dirpath,
+                                               &start);
+                       errno = saved_errno;
+                       if (!NT_STATUS_IS_OK(status)) {
                                goto fail;
                        }
                }
-               /* 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",
-                smb_fname->base_name, dirpath, start));
+               /*
+                * A special case - if we don't have any wildcards or mangling chars and are case
+                * sensitive or the underlying filesystem is case insensitive then searching
+                * won't help.
+                */
 
-       /*
-        * A special case - if we don't have any mangling chars and are case
-        * sensitive or the underlying filesystem is case insentive then searching
-        * won't help.
-        */
+               if ((conn->case_sensitive || !(conn->fs_capabilities &
+                                       FILE_CASE_SENSITIVE_SEARCH)) &&
+                               !mangle_is_mangled(smb_fname->base_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;
+                       status = check_for_dot_component(smb_fname);
+                       if (!NT_STATUS_IS_OK(status)) {
+                               goto fail;
+                       }
+
+                       /*
+                        * The stat failed. Could be ok as it could be
+                        * a new file.
+                        */
+
+                       if (errno == ENOTDIR || errno == ELOOP) {
+                               status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+                               goto fail;
+                       } else if (errno == ENOENT) {
+                               /*
+                                * Was it a missing last component ?
+                                * or a missing intermediate component ?
+                                */
+                               struct smb_filename parent_fname;
+                               const char *last_component = NULL;
+
+                               ZERO_STRUCT(parent_fname);
+                               if (!parent_dirname(ctx, smb_fname->base_name,
+                                                       &parent_fname.base_name,
+                                                       &last_component)) {
+                                       status = NT_STATUS_NO_MEMORY;
+                                       goto fail;
+                               }
+                               if (posix_pathnames) {
+                                       ret = SMB_VFS_LSTAT(conn, &parent_fname);
+                               } else {
+                                       ret = SMB_VFS_STAT(conn, &parent_fname);
+                               }
+                               if (ret == -1) {
+                                       if (errno == ENOTDIR ||
+                                                       errno == ENOENT ||
+                                                       errno == ELOOP) {
+                                               status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+                                               goto fail;
+                                       }
+                               }
+
+                               /*
+                                * Missing last component is ok - new file.
+                                * Also deal with permission denied elsewhere.
+                                * Just drop out to done.
+                                */
+                               goto done;
+                       }
+               }
+       } else {
+               /*
+                * We have a wildcard in the pathname.
+                *
+                * Optimization for common case where the wildcard
+                * is in the last component and the client already
+                * sent the correct case.
+                * NOTE : check_parent_exists() doesn't preserve errno.
+                */
+               int saved_errno = errno;
+               status = check_parent_exists(ctx,
+                                       conn,
+                                       posix_pathnames,
+                                       smb_fname,
+                                       &dirpath,
+                                       &start);
+               errno = saved_errno;
+               if (!NT_STATUS_IS_OK(status)) {
+                       goto fail;
+               }
        }
 
        /*
@@ -403,18 +632,18 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
 
                name_has_wildcard = ms_has_wild(start);
 
-               /* Wildcard not valid anywhere. */
-               if (name_has_wildcard && !allow_wcard_last_component) {
-                       status = NT_STATUS_OBJECT_NAME_INVALID;
-                       goto fail;
-               }
-
                /* Wildcards never valid within a pathname. */
                if (name_has_wildcard && end) {
                        status = NT_STATUS_OBJECT_NAME_INVALID;
                        goto fail;
                }
 
+               /* Skip the stat call if it's a wildcard end. */
+               if (name_has_wildcard) {
+                       DEBUG(5,("Wildcard %s\n",start));
+                       goto done;
+               }
+
                /*
                 * Check if the name exists up to this point.
                 */
@@ -511,12 +740,30 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
 
                                /*
                                 * 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.
+                                * here.
                                 */
-                               if ((errno != 0) && (errno != ENOENT)
-                                   && (errno != EACCES)) {
+
+                               if (errno == EACCES) {
+                                       if ((ucf_flags & UCF_PREP_CREATEFILE) == 0) {
+                                               status = NT_STATUS_ACCESS_DENIED;
+                                               goto fail;
+                                       } else {
+                                               /*
+                                                * This is the dropbox
+                                                * behaviour. A dropbox is a
+                                                * directory with only -wx
+                                                * permissions, so
+                                                * get_real_filename fails
+                                                * with EACCESS, it needs to
+                                                * list the directory. We
+                                                * nevertheless want to allow
+                                                * users creating a file.
+                                                */
+                                               errno = 0;
+                                       }
+                               }
+
+                               if ((errno != 0) && (errno != ENOENT)) {
                                        /*
                                         * ENOTDIR and ELOOP both map to
                                         * NT_STATUS_OBJECT_PATH_NOT_FOUND
@@ -544,8 +791,13 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                    (mangle_is_8_3(start, False,
                                                   conn->params) &&
                                                 !conn->short_case_preserve)) {
-                                       strnorm(start,
-                                               lp_defaultcase(SNUM(conn)));
+                                       if (!strnorm(start,
+                                                       lp_default_case(SNUM(conn)))) {
+                                               DEBUG(0, ("strnorm %s failed\n",
+                                                       start));
+                                               status = NT_STATUS_INVALID_PARAMETER;
+                                               goto err;
+                                       }
                                }
 
                                /*
@@ -667,8 +919,18 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                 */
                if (VALID_STAT(smb_fname->st)) {
                        bool delete_pending;
+                       uint32_t name_hash;
+
+                       status = file_name_hash(conn,
+                                       smb_fname_str_dbg(smb_fname),
+                                       &name_hash);
+                       if (!NT_STATUS_IS_OK(status)) {
+                               goto fail;
+                       }
+
                        get_file_infos(vfs_file_id_from_sbuf(conn,
                                                             &smb_fname->st),
+                                      name_hash,
                                       &delete_pending, NULL);
                        if (delete_pending) {
                                status = NT_STATUS_DELETE_PENDING;
@@ -741,7 +1003,7 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                smb_fname->stream_name = stream;
 
                /* Check path now that the base_name has been converted. */
-               status = build_stream_path(ctx, conn, orig_path, smb_fname);
+               status = build_stream_path(ctx, conn, smb_fname);
                if (!NT_STATUS_IS_OK(status)) {
                        goto fail;
                }
@@ -751,7 +1013,7 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
        return NT_STATUS_OK;
  fail:
        DEBUG(10, ("dirpath = [%s] start = [%s]\n", dirpath, start));
-       if (*dirpath != '\0') {
+       if (dirpath && *dirpath != '\0') {
                smb_fname->base_name = talloc_asprintf(smb_fname, "%s/%s",
                                                       dirpath, start);
        } else {
@@ -772,26 +1034,39 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
 }
 
 /****************************************************************************
- Check a filename - possibly calling check_reduced_name.
- This is called by every routine before it allows an operation on a filename.
- It does any final confirmation necessary to ensure that the filename is
- a valid one for the user to access.
+ Ensure a path is not vetoed.
 ****************************************************************************/
 
-NTSTATUS check_name(connection_struct *conn, const char *name)
+static NTSTATUS check_veto_path(connection_struct *conn, const char *name)
 {
        if (IS_VETO_PATH(conn, name))  {
                /* Is it not dot or dot dot. */
-               if (!((name[0] == '.') && (!name[1] ||
-                                       (name[1] == '.' && !name[2])))) {
-                       DEBUG(5,("check_name: file path name %s vetoed\n",
+               if (!(ISDOT(name) || ISDOTDOT(name))) {
+                       DEBUG(5,("check_veto_path: file path name %s vetoed\n",
                                                name));
                        return map_nt_error_from_unix(ENOENT);
                }
        }
+       return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Check a filename - possibly calling check_reduced_name.
+ This is called by every routine before it allows an operation on a filename.
+ It does any final confirmation necessary to ensure that the filename is
+ a valid one for the user to access.
+****************************************************************************/
+
+NTSTATUS check_name(connection_struct *conn, const char *name)
+{
+       NTSTATUS status = check_veto_path(conn, name);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
 
-       if (!lp_widelinks(SNUM(conn)) || !lp_symlinks(SNUM(conn))) {
-               NTSTATUS status = check_reduced_name(conn,name);
+       if (!lp_widelinks(SNUM(conn)) || !lp_follow_symlinks(SNUM(conn))) {
+               status = check_reduced_name(conn,name);
                if (!NT_STATUS_IS_OK(status)) {
                        DEBUG(5,("check_name: name %s failed with %s\n",name,
                                                nt_errstr(status)));
@@ -802,6 +1077,25 @@ NTSTATUS check_name(connection_struct *conn, const char *name)
        return NT_STATUS_OK;
 }
 
+/****************************************************************************
+ Must be called as root. Creates the struct privilege_paths
+ attached to the struct smb_request if this call is successful.
+****************************************************************************/
+
+static NTSTATUS check_name_with_privilege(connection_struct *conn,
+               struct smb_request *smbreq,
+               const char *name)
+{
+       NTSTATUS status = check_veto_path(conn, name);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+       return check_reduced_name_with_privilege(conn,
+                       name,
+                       smbreq);
+}
+
 /****************************************************************************
  Check if two filenames are equal.
  This needs to be careful about whether we are case sensitive.
@@ -829,7 +1123,8 @@ static int get_real_filename_full_scan(connection_struct *conn,
                                       TALLOC_CTX *mem_ctx, char **found_name)
 {
        struct smb_Dir *cur_dir;
-       char *dname = NULL;
+       const char *dname = NULL;
+       char *talloced = NULL;
        char *unmangled_name = NULL;
        long curpos;
 
@@ -881,11 +1176,11 @@ static int get_real_filename_full_scan(connection_struct *conn,
 
        /* now scan for matching names */
        curpos = 0;
-       while ((dname = ReadDirName(cur_dir, &curpos, NULL))) {
+       while ((dname = ReadDirName(cur_dir, &curpos, NULL, &talloced))) {
 
                /* Is it dot or dot dot. */
                if (ISDOT(dname) || ISDOTDOT(dname)) {
-                       TALLOC_FREE(dname);
+                       TALLOC_FREE(talloced);
                        continue;
                }
 
@@ -908,13 +1203,13 @@ static int get_real_filename_full_scan(connection_struct *conn,
                        TALLOC_FREE(cur_dir);
                        if (!*found_name) {
                                errno = ENOMEM;
-                               TALLOC_FREE(dname);
+                               TALLOC_FREE(talloced);
                                return -1;
                        }
-                       TALLOC_FREE(dname);
+                       TALLOC_FREE(talloced);
                        return 0;
                }
-               TALLOC_FREE(dname);
+               TALLOC_FREE(talloced);
        }
 
        TALLOC_FREE(unmangled_name);
@@ -960,11 +1255,10 @@ int get_real_filename(connection_struct *conn, const char *path,
 
 static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
                                  connection_struct *conn,
-                                 const char *orig_path,
                                  struct smb_filename *smb_fname)
 {
        NTSTATUS status;
-       unsigned int i, num_streams;
+       unsigned int i, num_streams = 0;
        struct stream_struct *streams = NULL;
 
        if (SMB_VFS_STAT(conn, smb_fname) == 0) {
@@ -973,14 +1267,14 @@ static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
        }
 
        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;
        }
 
        /* 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);
+       status = 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(smb_fname->st);
@@ -1038,6 +1332,7 @@ static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
  * @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 smbreq       SMB request if we're using privileges.
  * @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
@@ -1051,15 +1346,17 @@ static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
  * @return NT_STATUS_OK if all operations completed succesfully, appropriate
  *        error otherwise.
  */
-NTSTATUS filename_convert(TALLOC_CTX *ctx,
+static NTSTATUS filename_convert_internal(TALLOC_CTX *ctx,
                                connection_struct *conn,
                                bool dfs_path,
+                               struct smb_request *smbreq,
                                const char *name_in,
                                uint32_t ucf_flags,
                                bool *ppath_contains_wcard,
                                struct smb_filename **pp_smb_fname)
 {
        NTSTATUS status;
+       bool allow_wcards = (ucf_flags & (UCF_COND_ALLOW_WCARD_LCOMP|UCF_ALWAYS_ALLOW_WCARD_LCOMP));
        char *fname = NULL;
 
        *pp_smb_fname = NULL;
@@ -1067,10 +1364,12 @@ NTSTATUS filename_convert(TALLOC_CTX *ctx,
        status = resolve_dfspath_wcard(ctx, conn,
                                dfs_path,
                                name_in,
+                               allow_wcards,
+                               !conn->sconn->using_smb2,
                                &fname,
                                ppath_contains_wcard);
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(10,("filename_convert: resolve_dfspath failed "
+               DEBUG(10,("filename_convert_internal: resolve_dfspath failed "
                        "for name %s with %s\n",
                        name_in,
                        nt_errstr(status) ));
@@ -1081,11 +1380,13 @@ NTSTATUS filename_convert(TALLOC_CTX *ctx,
                SMB_STRUCT_STAT st;
                ZERO_STRUCT(st);
                st.st_ex_nlink = 1;
-               status = create_synthetic_smb_fname_split(ctx,
+               *pp_smb_fname = synthetic_smb_fname_split(ctx,
                                                          name_in,
-                                                         &st,
-                                                         pp_smb_fname);
-               return status;
+                                                         &st);
+               if (*pp_smb_fname == NULL) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+               return NT_STATUS_OK;
        }
 
        /*
@@ -1099,16 +1400,26 @@ NTSTATUS filename_convert(TALLOC_CTX *ctx,
 
        status = unix_convert(ctx, conn, fname, pp_smb_fname, ucf_flags);
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(10,("filename_convert: unix_convert failed "
+               DEBUG(10,("filename_convert_internal: 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 ((ucf_flags & UCF_UNIX_NAME_LOOKUP) &&
+                       VALID_STAT((*pp_smb_fname)->st) &&
+                       S_ISLNK((*pp_smb_fname)->st.st_ex_mode)) {
+               return check_veto_path(conn, (*pp_smb_fname)->base_name);
+       }
+
+       if (!smbreq) {
+               status = check_name(conn, (*pp_smb_fname)->base_name);
+       } else {
+               status = check_name_with_privilege(conn, smbreq, (*pp_smb_fname)->base_name);
+       }
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(3,("filename_convert: check_name failed "
+               DEBUG(3,("filename_convert_internal: check_name failed "
                        "for name %s with %s\n",
                        smb_fname_str_dbg(*pp_smb_fname),
                        nt_errstr(status) ));
@@ -1118,3 +1429,49 @@ NTSTATUS filename_convert(TALLOC_CTX *ctx,
 
        return status;
 }
+
+/*
+ * Go through all the steps to validate a filename.
+ * Non-root version.
+ */
+
+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)
+{
+       return filename_convert_internal(ctx,
+                                       conn,
+                                       dfs_path,
+                                       NULL,
+                                       name_in,
+                                       ucf_flags,
+                                       ppath_contains_wcard,
+                                       pp_smb_fname);
+}
+
+/*
+ * Go through all the steps to validate a filename.
+ * root (privileged) version.
+ */
+
+NTSTATUS filename_convert_with_privilege(TALLOC_CTX *ctx,
+                                connection_struct *conn,
+                               struct smb_request *smbreq,
+                                const char *name_in,
+                                uint32_t ucf_flags,
+                                bool *ppath_contains_wcard,
+                                struct smb_filename **pp_smb_fname)
+{
+       return filename_convert_internal(ctx,
+                                       conn,
+                                       smbreq->flags2 & FLAGS2_DFS_PATHNAMES,
+                                       smbreq,
+                                       name_in,
+                                       ucf_flags,
+                                       ppath_contains_wcard,
+                                       pp_smb_fname);
+}