s3: smbd: Ensure dirpath is set to ".", not "\0" so it is always a valid path.
[vlendec/samba-autobuild/.git] / source3 / smbd / filename.c
index 7fe7cc8063b1f71ba2ba29b536f481efed87accd..16d0f340102672766807d071f04c9d44029a6987 100644 (file)
 #include "system/filesys.h"
 #include "fake_file.h"
 #include "smbd/smbd.h"
+#include "smbd/globals.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;
+}
 
 static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
                                  connection_struct *conn,
-                                 const char *orig_path,
                                  struct smb_filename *smb_fname);
 
 /****************************************************************************
@@ -55,9 +95,11 @@ static bool mangled_equal(const char *name1,
 ****************************************************************************/
 
 static NTSTATUS determine_path_error(const char *name,
-                       bool allow_wcard_last_component)
+                       bool allow_wcard_last_component,
+                       bool posix_pathnames)
 {
        const char *p;
+       bool name_has_wild = false;
 
        if (!allow_wcard_last_component) {
                /* Error code within a pathname. */
@@ -74,7 +116,11 @@ static NTSTATUS determine_path_error(const char *name,
 
        p = strchr(name, '/');
 
-       if (!p && (ms_has_wild(name) || ISDOT(name))) {
+       if (!posix_pathnames) {
+               name_has_wild = ms_has_wild(name);
+       }
+
+       if (!p && (name_has_wild || ISDOT(name))) {
                /* Error code at the end of a pathname. */
                return NT_STATUS_OBJECT_NAME_INVALID;
        } else {
@@ -118,27 +164,30 @@ static NTSTATUS check_parent_exists(TALLOC_CTX *ctx,
                                char **pp_dirpath,
                                char **pp_start)
 {
-       struct smb_filename parent_fname;
+       struct smb_filename parent_fname = {0};
        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 (!posix_pathnames) {
+               if (ms_has_wild(parent_fname.base_name)) {
+                       goto no_optimization_out;
+               }
+       }
+
        /*
         * If there was no parent component in
-        * smb_fname->base_name of the parent name
-        * contained a wildcard then don't do this
+        * smb_fname->base_name 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 (smb_fname->base_name == last_component) {
+               goto no_optimization_out;
        }
 
        if (posix_pathnames) {
@@ -151,7 +200,7 @@ static NTSTATUS check_parent_exists(TALLOC_CTX *ctx,
           with the normal tree walk. */
 
        if (ret == -1) {
-               return NT_STATUS_OK;
+               goto no_optimization_out;
        }
 
        status = check_for_dot_component(&parent_fname);
@@ -160,7 +209,7 @@ static NTSTATUS check_parent_exists(TALLOC_CTX *ctx,
        }
 
        /* Parent exists - set "start" to be the
-        * last compnent to shorten the tree walk. */
+        * last component to shorten the tree walk. */
 
        /*
         * Safe to use discard_const_p
@@ -184,6 +233,170 @@ static NTSTATUS check_parent_exists(TALLOC_CTX *ctx,
                *pp_start));
 
        return NT_STATUS_OK;
+
+  no_optimization_out:
+
+       /*
+        * We must still return an *pp_dirpath
+        * initialized to ".", and a *pp_start
+        * pointing at smb_fname->base_name.
+        */
+
+       TALLOC_FREE(parent_fname.base_name);
+
+       *pp_dirpath = talloc_strdup(ctx, ".");
+       if (*pp_dirpath == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       /*
+        * Safe to use discard_const_p
+        * here as by convention smb_fname->base_name
+        * is allocated off ctx.
+        */
+       *pp_start = discard_const_p(char, smb_fname->base_name);
+       return NT_STATUS_OK;
+}
+
+/*
+ * Re-order a known good @GMT-token path.
+ */
+
+static NTSTATUS rearrange_snapshot_path(struct smb_filename *smb_fname,
+                               char *startp,
+                               char *endp)
+{
+       size_t endlen = 0;
+       size_t gmt_len = endp - startp;
+       char gmt_store[gmt_len + 1];
+       char *parent = NULL;
+       const char *last_component = NULL;
+       char *newstr;
+       bool ret;
+
+       DBG_DEBUG("|%s| -> ", smb_fname->base_name);
+
+       /* Save off the @GMT-token. */
+       memcpy(gmt_store, startp, gmt_len);
+       gmt_store[gmt_len] = '\0';
+
+       if (*endp == '/') {
+               /* Remove any trailing '/' */
+               endp++;
+       }
+
+       if (*endp == '\0') {
+               /*
+                * @GMT-token was at end of path.
+                * Remove any preceeding '/'
+                */
+               if (startp > smb_fname->base_name && startp[-1] == '/') {
+                       startp--;
+               }
+       }
+
+       /* Remove @GMT-token from the path. */
+       endlen = strlen(endp);
+       memmove(startp, endp, endlen + 1);
+
+       /* Split the remaining path into components. */
+       ret = parent_dirname(smb_fname,
+                               smb_fname->base_name,
+                               &parent,
+                               &last_component);
+       if (ret == false) {
+               /* Must terminate debug with \n */
+               DBG_DEBUG("NT_STATUS_NO_MEMORY\n");
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       if (ISDOT(parent)) {
+               if (last_component[0] == '\0') {
+                       newstr = talloc_strdup(smb_fname,
+                                       gmt_store);
+               } else {
+                       newstr = talloc_asprintf(smb_fname,
+                                       "%s/%s",
+                                       gmt_store,
+                                       last_component);
+               }
+       } else {
+               newstr = talloc_asprintf(smb_fname,
+                                       "%s/%s/%s",
+                                       gmt_store,
+                                       parent,
+                                       last_component);
+       }
+
+       TALLOC_FREE(parent);
+       TALLOC_FREE(smb_fname->base_name);
+       smb_fname->base_name = newstr;
+
+       DBG_DEBUG("|%s|\n", newstr);
+
+       return NT_STATUS_OK;
+}
+
+/*
+ * Canonicalize any incoming pathname potentially containining
+ * a @GMT-token into a path that looks like:
+ *
+ * @GMT-YYYY-MM-DD-HH-MM-SS/path/name/components/last_component
+ *
+ * Leaves single path @GMT-token -component alone:
+ *
+ * @GMT-YYYY-MM-DD-HH-MM-SS -> @GMT-YYYY-MM-DD-HH-MM-SS
+ *
+ * Eventually when struct smb_filename is updated and the VFS
+ * ABI is changed this will remove the @GMT-YYYY-MM-DD-HH-MM-SS
+ * and store in the struct smb_filename as a struct timeval field
+ * instead.
+ */
+
+static NTSTATUS canonicalize_snapshot_path(struct smb_filename *smb_fname)
+{
+       char *startp = strchr_m(smb_fname->base_name, '@');
+       char *endp = NULL;
+       struct tm tm;
+
+       if (startp == NULL) {
+               /* No @ */
+               return NT_STATUS_OK;
+       }
+
+       startp = strstr_m(startp, "@GMT-");
+       if (startp == NULL) {
+               /* No @ */
+               return NT_STATUS_OK;
+       }
+
+       if ((startp > smb_fname->base_name) && (startp[-1] != '/')) {
+               /* the GMT-token does not start a path-component */
+               return NT_STATUS_OK;
+       }
+
+       endp = strptime(startp, GMT_FORMAT, &tm);
+       if (endp == NULL) {
+               /* Not a valid timestring. */
+               return NT_STATUS_OK;
+       }
+
+       if ( endp[0] == '\0') {
+               return rearrange_snapshot_path(smb_fname,
+                                       startp,
+                                       endp);
+       }
+
+       if (endp[0] != '/') {
+               /*
+                * It is not a complete path component, i.e. the path
+                * component continues after the gmt-token.
+                */
+               return NT_STATUS_OK;
+       }
+
+       return rearrange_snapshot_path(smb_fname,
+                               startp,
+                               endp);
 }
 
 /****************************************************************************
@@ -224,15 +437,24 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                      uint32_t ucf_flags)
 {
        struct smb_filename *smb_fname = NULL;
-       char *start, *end;
+
+       /*
+        * This looks strange. But we need "start" initialized to "" here but
+        * it can't be a const char *, so 'char *start = "";' does not work.
+        */
+       char cnull = '\0';
+       char *start = &cnull;
+
+       char *end;
        char *dirpath = NULL;
        char *stream = NULL;
        bool component_was_mangled = False;
        bool name_has_wildcard = False;
-       bool posix_pathnames = false;
+       bool posix_pathnames = (ucf_flags & UCF_POSIX_PATHNAMES);
        bool allow_wcard_last_component =
            (ucf_flags & UCF_ALWAYS_ALLOW_WCARD_LCOMP);
        bool save_last_component = ucf_flags & UCF_SAVE_LCOMP;
+       bool snapshot_path = (ucf_flags & UCF_GMT_PATHNAME);
        NTSTATUS status;
        int ret = -1;
 
@@ -254,6 +476,8 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                goto done;
        }
 
+       smb_fname->flags = posix_pathnames ? SMB_FILENAME_POSIX_PATH : 0;
+
        DEBUG(5, ("unix_convert called on file \"%s\"\n", orig_path));
 
        /*
@@ -299,7 +523,8 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                        status = NT_STATUS_OBJECT_NAME_INVALID;
                } else {
                        status =determine_path_error(&orig_path[2],
-                           allow_wcard_last_component);
+                           allow_wcard_last_component,
+                           posix_pathnames);
                }
                goto err;
        }
@@ -311,6 +536,14 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                goto err;
        }
 
+       /* Canonicalize any @GMT- paths. */
+       if (snapshot_path) {
+               status = canonicalize_snapshot_path(smb_fname);
+               if (!NT_STATUS_IS_OK(status)) {
+                       goto err;
+               }
+       }
+
        /*
         * Large directory fix normalization. If we're case sensitive, and
         * the case preserving parameters are set to "no", normalize the case of
@@ -322,7 +555,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;
+               }
        }
 
        /*
@@ -344,9 +581,6 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                }
        }
 
-       posix_pathnames = (lp_posix_pathnames() ||
-                               (ucf_flags & UCF_POSIX_PATHNAMES));
-
        /*
         * Strip off the stream, and add it back when we're done with the
         * base_name.
@@ -366,6 +600,35 @@ 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;
+                               }
+                               /* dirpath must exist. */
+                               dirpath = talloc_strdup(ctx,".");
+                               if (dirpath == NULL) {
+                                       status = NT_STATUS_NO_MEMORY;
+                                       goto err;
+                               }
+                               DEBUG(5, ("conversion finished %s -> %s\n",
+                                       orig_path,
+                                       smb_fname->base_name));
+                               goto done;
+                       }
                }
        }
 
@@ -383,7 +646,7 @@ 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;
        }
@@ -393,7 +656,7 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
         * building the directories with talloc_asprintf and free it.
         */
 
-       if ((dirpath == NULL) && (!(dirpath = talloc_strdup(ctx,"")))) {
+       if ((dirpath == NULL) && (!(dirpath = talloc_strdup(ctx,".")))) {
                DEBUG(0, ("talloc_strdup failed\n"));
                status = NT_STATUS_NO_MEMORY;
                goto err;
@@ -405,11 +668,14 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
         * is true.
         */
 
-       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 (!posix_pathnames) {
+               /* POSIX pathnames have no wildcards. */
+               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;
+               }
        }
 
        DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n",
@@ -445,13 +711,17 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
 
                if (errno == ENOENT) {
                        /* Optimization when creating a new file - only
-                          the last component doesn't exist. */
+                          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;
                        }
@@ -524,13 +794,16 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                 * 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;
                }
@@ -588,7 +861,8 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                status = NT_STATUS_OBJECT_NAME_INVALID;
                        } else {
                                status = determine_path_error(end+1,
-                                               allow_wcard_last_component);
+                                               allow_wcard_last_component,
+                                               posix_pathnames);
                        }
                        goto fail;
                }
@@ -596,7 +870,9 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                /* The name cannot have a wildcard if it's not
                   the last component. */
 
-               name_has_wildcard = ms_has_wild(start);
+               if (!posix_pathnames) {
+                       name_has_wildcard = ms_has_wild(start);
+               }
 
                /* Wildcards never valid within a pathname. */
                if (name_has_wildcard && end) {
@@ -706,12 +982,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
@@ -739,8 +1033,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;
+                                       }
                                }
 
                                /*
@@ -757,7 +1056,7 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                        size_t start_ofs =
                                            start - smb_fname->base_name;
 
-                                       if (*dirpath != '\0') {
+                                       if (!ISDOT(dirpath)) {
                                                tmp = talloc_asprintf(
                                                        smb_fname, "%s/%s",
                                                        dirpath, unmangled);
@@ -792,7 +1091,7 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                size_t start_ofs =
                                    start - smb_fname->base_name;
 
-                               if (*dirpath != '\0') {
+                               if (!ISDOT(dirpath)) {
                                        tmp = talloc_asprintf(smb_fname,
                                                "%s/%s/%s", dirpath,
                                                found_name, end+1);
@@ -817,7 +1116,7 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                                size_t start_ofs =
                                    start - smb_fname->base_name;
 
-                               if (*dirpath != '\0') {
+                               if (!ISDOT(dirpath)) {
                                        tmp = talloc_asprintf(smb_fname,
                                                "%s/%s", dirpath,
                                                found_name);
@@ -854,39 +1153,11 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
                        TALLOC_FREE(found_name);
                } /* end else */
 
-#ifdef DEVELOPER
-               /*
-                * This sucks!
-                * We should never provide different behaviors
-                * depending on DEVELOPER!!!
-                */
-               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;
-                               goto fail;
-                       }
-               }
-#endif
-
                /*
                 * Add to the dirpath that we have resolved so far.
                 */
 
-               if (*dirpath != '\0') {
+               if (!ISDOT(dirpath)) {
                        char *tmp = talloc_asprintf(ctx,
                                        "%s/%s", dirpath, start);
                        if (!tmp) {
@@ -946,7 +1217,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;
                }
@@ -956,7 +1227,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 && !ISDOT(dirpath)) {
                smb_fname->base_name = talloc_asprintf(smb_fname, "%s/%s",
                                                       dirpath, start);
        } else {
@@ -977,28 +1248,47 @@ 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 struct smb_filename *smb_fname)
 {
+       const char *name = smb_fname->base_name;
+
        if (IS_VETO_PATH(conn, name))  {
                /* Is it not dot or dot dot. */
                if (!(ISDOT(name) || ISDOTDOT(name))) {
-                       DEBUG(5,("check_name: file path name %s vetoed\n",
+                       DEBUG(5,("check_veto_path: file path name %s vetoed\n",
                                                name));
                        return map_nt_error_from_unix(ENOENT);
                }
        }
+       return NT_STATUS_OK;
+}
 
-       if (!lp_widelinks(SNUM(conn)) || !lp_symlinks(SNUM(conn))) {
-               NTSTATUS status = check_reduced_name(conn,name);
+/****************************************************************************
+ 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 struct smb_filename *smb_fname)
+{
+       NTSTATUS status = check_veto_path(conn, smb_fname);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       if (!lp_widelinks(SNUM(conn)) || !lp_follow_symlinks(SNUM(conn))) {
+               status = check_reduced_name(conn, NULL, smb_fname);
                if (!NT_STATUS_IS_OK(status)) {
-                       DEBUG(5,("check_name: name %s failed with %s\n",name,
-                                               nt_errstr(status)));
+                       DEBUG(5,("check_name: name %s failed with %s\n",
+                                       smb_fname->base_name,
+                                       nt_errstr(status)));
                        return status;
                }
        }
@@ -1006,6 +1296,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 struct smb_filename *smb_fname)
+{
+       NTSTATUS status = check_veto_path(conn, smb_fname);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+       return check_reduced_name_with_privilege(conn,
+                       smb_fname,
+                       smbreq);
+}
+
 /****************************************************************************
  Check if two filenames are equal.
  This needs to be careful about whether we are case sensitive.
@@ -1037,6 +1346,7 @@ static int get_real_filename_full_scan(connection_struct *conn,
        char *talloced = NULL;
        char *unmangled_name = NULL;
        long curpos;
+       struct smb_filename *smb_fname = NULL;
 
        /* handle null paths */
        if ((path == NULL) || (*path == 0)) {
@@ -1077,13 +1387,26 @@ static int get_real_filename_full_scan(connection_struct *conn,
                }
        }
 
+       smb_fname = synthetic_smb_fname(talloc_tos(),
+                                       path,
+                                       NULL,
+                                       NULL,
+                                       0);
+       if (smb_fname == NULL) {
+               TALLOC_FREE(unmangled_name);
+               return -1;
+       }
+
        /* open the directory */
-       if (!(cur_dir = OpenDir(talloc_tos(), conn, path, NULL, 0))) {
+       if (!(cur_dir = OpenDir(talloc_tos(), conn, smb_fname, NULL, 0))) {
                DEBUG(3,("scan dir didn't open dir [%s]\n",path));
                TALLOC_FREE(unmangled_name);
+               TALLOC_FREE(smb_fname);
                return -1;
        }
 
+       TALLOC_FREE(smb_fname);
+
        /* now scan for matching names */
        curpos = 0;
        while ((dname = ReadDirName(cur_dir, &curpos, NULL, &talloced))) {
@@ -1140,6 +1463,11 @@ int get_real_filename(connection_struct *conn, const char *path,
        int ret;
        bool mangled;
 
+       /* handle null paths */
+       if ((path == NULL) || (*path == 0)) {
+               path = ".";
+       }
+
        mangled = mangle_is_mangled(name, conn->params);
 
        if (mangled) {
@@ -1165,7 +1493,6 @@ 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;
@@ -1184,8 +1511,8 @@ static NTSTATUS build_stream_path(TALLOC_CTX *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);
+       status = vfs_streaminfo(conn, NULL, smb_fname, mem_ctx,
+                               &num_streams, &streams);
 
        if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
                SET_STAT_INVALID(smb_fname->st);
@@ -1242,7 +1569,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
@@ -1253,46 +1580,56 @@ static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
  * @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
+ * @return NT_STATUS_OK if all operations completed successfully, 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;
 
-       status = resolve_dfspath_wcard(ctx, conn,
-                               dfs_path,
+       if (ucf_flags & UCF_DFS_PATHNAME) {
+               bool path_contains_wcard = false;
+               char *fname = NULL;
+               status = resolve_dfspath_wcard(ctx, conn,
                                name_in,
-                               allow_wcards,
+                               ucf_flags,
+                               !conn->sconn->using_smb2,
                                &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;
+                               &path_contains_wcard);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(10,("filename_convert_internal: resolve_dfspath "
+                               "failed for name %s with %s\n",
+                               name_in,
+                               nt_errstr(status) ));
+                       return status;
+               }
+               name_in = fname;
+               if (ppath_contains_wcard != NULL && path_contains_wcard) {
+                       *ppath_contains_wcard = path_contains_wcard;
+               }
+               ucf_flags &= ~UCF_DFS_PATHNAME;
        }
 
        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;
+               *pp_smb_fname = synthetic_smb_fname_split(ctx,
+                                       name_in,
+                                       (ucf_flags & UCF_POSIX_PATHNAMES));
+               if (*pp_smb_fname == NULL) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+               (*pp_smb_fname)->st = st;
+               return NT_STATUS_OK;
        }
 
        /*
@@ -1304,18 +1641,29 @@ NTSTATUS filename_convert(TALLOC_CTX *ctx,
                ucf_flags |= UCF_ALWAYS_ALLOW_WCARD_LCOMP;
        }
 
-       status = unix_convert(ctx, conn, fname, pp_smb_fname, ucf_flags);
+       status = unix_convert(ctx, conn, name_in, 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,
+                       name_in,
                        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));
+       }
+
+       if (!smbreq) {
+               status = check_name(conn, (*pp_smb_fname));
+       } else {
+               status = check_name_with_privilege(conn, smbreq,
+                               (*pp_smb_fname));
+       }
        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) ));
@@ -1325,3 +1673,46 @@ 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,
+                               const char *name_in,
+                               uint32_t ucf_flags,
+                               bool *ppath_contains_wcard,
+                               struct smb_filename **pp_smb_fname)
+{
+       return filename_convert_internal(ctx,
+                                       conn,
+                                       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,
+                                       name_in,
+                                       ucf_flags,
+                                       ppath_contains_wcard,
+                                       pp_smb_fname);
+}