smbd: Simplify openat_pathref_fsp_case_insensitive()
[samba.git] / source3 / smbd / files.c
index 4f977f7235bc0d23b85c952624060e15f0f4be23..b94468ad6afd7da1700115256cb771c2dedbe8ea 100644 (file)
@@ -25,6 +25,7 @@
 #include "util_tdb.h"
 #include "lib/util/bitmap.h"
 #include "lib/util/strv.h"
+#include "lib/util/memcache.h"
 #include "libcli/smb/reparse.h"
 
 #define FILE_HANDLE_OFFSET 0x1000
@@ -831,6 +832,135 @@ NTSTATUS create_open_symlink_err(TALLOC_CTX *mem_ctx,
        return NT_STATUS_OK;
 }
 
+static int smb_vfs_openat_ci(TALLOC_CTX *mem_ctx,
+                            bool case_sensitive,
+                            struct connection_struct *conn,
+                            struct files_struct *dirfsp,
+                            struct smb_filename *smb_fname_rel,
+                            files_struct *fsp,
+                            const struct vfs_open_how *how)
+{
+       char *orig_base_name = smb_fname_rel->base_name;
+       DATA_BLOB cache_key = {
+               .data = NULL,
+       };
+       DATA_BLOB cache_value = {
+               .data = NULL,
+       };
+       NTSTATUS status;
+       int fd;
+       bool ok;
+
+       fd = SMB_VFS_OPENAT(conn, dirfsp, smb_fname_rel, fsp, how);
+       if ((fd >= 0) || case_sensitive) {
+               return fd;
+       }
+       if (errno != ENOENT) {
+               return -1;
+       }
+
+       if (!lp_stat_cache()) {
+               goto lookup;
+       }
+
+       ok = get_real_filename_cache_key(mem_ctx,
+                                        dirfsp,
+                                        orig_base_name,
+                                        &cache_key);
+       if (!ok) {
+               /*
+                * probably ENOMEM, just bail
+                */
+               errno = ENOMEM;
+               return -1;
+       }
+
+       DO_PROFILE_INC(statcache_lookups);
+
+       ok = memcache_lookup(NULL,
+                            GETREALFILENAME_CACHE,
+                            cache_key,
+                            &cache_value);
+       if (!ok) {
+               DO_PROFILE_INC(statcache_misses);
+               goto lookup;
+       }
+       DO_PROFILE_INC(statcache_hits);
+
+       smb_fname_rel->base_name = talloc_strndup(mem_ctx,
+                                                 (char *)cache_value.data,
+                                                 cache_value.length);
+       if (smb_fname_rel->base_name == NULL) {
+               TALLOC_FREE(cache_key.data);
+               smb_fname_rel->base_name = orig_base_name;
+               errno = ENOMEM;
+               return -1;
+       }
+
+       if (IS_VETO_PATH(dirfsp->conn, smb_fname_rel->base_name)) {
+               DBG_DEBUG("veto files rejecting last component %s\n",
+                         smb_fname_str_dbg(smb_fname_rel));
+               TALLOC_FREE(cache_key.data);
+               smb_fname_rel->base_name = orig_base_name;
+               errno = EPERM;
+               return -1;
+       }
+
+       fd = SMB_VFS_OPENAT(conn, dirfsp, smb_fname_rel, fsp, how);
+       if (fd >= 0) {
+               TALLOC_FREE(cache_key.data);
+               return fd;
+       }
+
+       memcache_delete(NULL, GETREALFILENAME_CACHE, cache_key);
+
+       /*
+        * For the "new filename" case we need to preserve the
+        * capitalization the client sent us, see
+        * https://bugzilla.samba.org/show_bug.cgi?id=15481
+        */
+       TALLOC_FREE(smb_fname_rel->base_name);
+       smb_fname_rel->base_name = orig_base_name;
+
+lookup:
+
+       status = get_real_filename_at(dirfsp,
+                                     orig_base_name,
+                                     mem_ctx,
+                                     &smb_fname_rel->base_name);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_DEBUG("get_real_filename_at() failed: %s\n",
+                         nt_errstr(status));
+               errno = ENOENT;
+               return -1;
+       }
+
+       if (IS_VETO_PATH(conn, smb_fname_rel->base_name)) {
+               DBG_DEBUG("found veto files path component "
+                         "%s => %s\n",
+                         orig_base_name,
+                         smb_fname_rel->base_name);
+               TALLOC_FREE(smb_fname_rel->base_name);
+               smb_fname_rel->base_name = orig_base_name;
+               errno = ENOENT;
+               return -1;
+       }
+
+       fd = SMB_VFS_OPENAT(conn, dirfsp, smb_fname_rel, fsp, how);
+
+       if ((fd >= 0) && (cache_key.data != NULL)) {
+               DATA_BLOB value = {
+                       .data = (uint8_t *)smb_fname_rel->base_name,
+                       .length = strlen(smb_fname_rel->base_name) + 1,
+               };
+
+               memcache_add(NULL, GETREALFILENAME_CACHE, cache_key, value);
+               TALLOC_FREE(cache_key.data);
+       }
+
+       return fd;
+}
+
 NTSTATUS openat_pathref_fsp_nosymlink(TALLOC_CTX *mem_ctx,
                                      struct connection_struct *conn,
                                      struct files_struct *in_dirfsp,
@@ -855,7 +985,7 @@ NTSTATUS openat_pathref_fsp_nosymlink(TALLOC_CTX *mem_ctx,
        struct open_symlink_err *symlink_err = NULL;
        struct files_struct *fsp = NULL;
        char *path = NULL, *next = NULL;
-       bool case_sensitive, ok, is_toplevel;
+       bool ok, is_toplevel;
        int fd;
        NTSTATUS status;
        struct vfs_open_how how = {
@@ -1030,48 +1160,13 @@ NTSTATUS openat_pathref_fsp_nosymlink(TALLOC_CTX *mem_ctx,
 next:
        next = strv_next(path, rel_fname.base_name);
 
-       fd = SMB_VFS_OPENAT(
-               conn,
-               dirfsp,
-               &rel_fname,
-               fsp,
-               &how);
-
-       case_sensitive = (posix || conn->case_sensitive);
-
-       if ((fd == -1) && (errno == ENOENT) && !case_sensitive) {
-               const char *orig_base_name = rel_fname.base_name;
-
-               status = get_real_filename_at(
-                       dirfsp,
-                       rel_fname.base_name,
-                       talloc_tos(),
-                       &rel_fname.base_name);
-
-               if (!NT_STATUS_IS_OK(status)) {
-                       DBG_DEBUG("get_real_filename_at failed: %s\n",
-                                 nt_errstr(status));
-                       goto fail;
-               }
-
-               /* Name might have been demangled - check veto files. */
-               if (IS_VETO_PATH(conn, rel_fname.base_name)) {
-                       DBG_DEBUG("%s contains veto files path component "
-                                 "%s => %s\n",
-                                 path_in,
-                                 orig_base_name,
-                                 rel_fname.base_name);
-                       status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
-                       goto fail;
-               }
-
-               fd = SMB_VFS_OPENAT(
-                       conn,
-                       dirfsp,
-                       &rel_fname,
-                       fsp,
-                       &how);
-       }
+       fd = smb_vfs_openat_ci(talloc_tos(),
+                              posix || conn->case_sensitive,
+                              conn,
+                              dirfsp,
+                              &rel_fname,
+                              fsp,
+                              &how);
 
 #ifndef O_PATH
        if ((fd == -1) && (errno == ELOOP)) {
@@ -1304,6 +1399,162 @@ fail:
        return status;
 }
 
+/*
+ * Open smb_fname_rel->fsp as a pathref fsp with a case insensitive
+ * fallback using GETREALFILENAME_CACHE and get_real_filename_at() if
+ * the first attempt based on the filename sent by the client gives
+ * ENOENT.
+ */
+NTSTATUS openat_pathref_fsp_lcomp(struct files_struct *dirfsp,
+                                 struct smb_filename *smb_fname_rel,
+                                 uint32_t ucf_flags)
+{
+       struct connection_struct *conn = dirfsp->conn;
+       const char *orig_rel_base_name = smb_fname_rel->base_name;
+       struct files_struct *fsp = NULL;
+       struct smb_filename *full_fname = NULL;
+       struct vfs_open_how how = {
+               .flags = O_RDONLY | O_NONBLOCK | O_NOFOLLOW,
+       };
+       NTSTATUS status;
+       int ret, fd;
+
+       /*
+        * Make sure we don't need of the all the magic in
+        * openat_pathref_fsp() with regards non_widelink_open etc.
+        */
+
+       SMB_ASSERT((smb_fname_rel->fsp == NULL) &&
+                  (dirfsp != dirfsp->conn->cwd_fsp) &&
+                  (strchr_m(smb_fname_rel->base_name, '/') == NULL) &&
+                  !is_named_stream(smb_fname_rel));
+
+       SET_STAT_INVALID(smb_fname_rel->st);
+
+       /* Check veto files - only looks at last component. */
+       if (IS_VETO_PATH(dirfsp->conn, smb_fname_rel->base_name)) {
+               DBG_DEBUG("veto files rejecting last component %s\n",
+                         smb_fname_str_dbg(smb_fname_rel));
+               return NT_STATUS_NETWORK_OPEN_RESTRICTION;
+       }
+
+       status = fsp_new(conn, conn, &fsp);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_DEBUG("fsp_new() failed: %s\n", nt_errstr(status));
+               return status;
+       }
+
+       GetTimeOfDay(&fsp->open_time);
+       fsp_set_gen_id(fsp);
+       ZERO_STRUCT(conn->sconn->fsp_fi_cache);
+
+       fsp->fsp_flags.is_pathref = true;
+
+       full_fname = full_path_from_dirfsp_atname(conn, dirfsp, smb_fname_rel);
+       if (full_fname == NULL) {
+               DBG_DEBUG("full_path_from_dirfsp_atname(%s/%s) failed\n",
+                         dirfsp->fsp_name->base_name,
+                         smb_fname_rel->base_name);
+               file_free(NULL, fsp);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       status = fsp_attach_smb_fname(fsp, &full_fname);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_DEBUG("fsp_attach_smb_fname(fsp, %s) failed: %s\n",
+                         smb_fname_str_dbg(full_fname),
+                         nt_errstr(status));
+               file_free(NULL, fsp);
+               return status;
+       }
+
+       fd = smb_vfs_openat_ci(smb_fname_rel,
+                              (ucf_flags & UCF_POSIX_PATHNAMES) ||
+                                      conn->case_sensitive,
+                              conn,
+                              dirfsp,
+                              smb_fname_rel,
+                              fsp,
+                              &how);
+
+       if ((fd == -1) && (errno == ENOENT)) {
+               status = map_nt_error_from_unix(errno);
+               DBG_DEBUG("smb_vfs_openat(%s/%s) failed: %s\n",
+                         dirfsp->fsp_name->base_name,
+                         smb_fname_rel->base_name,
+                         strerror(errno));
+               file_free(NULL, fsp);
+               return status;
+       }
+
+       if (smb_fname_rel->base_name != orig_rel_base_name) {
+               struct smb_filename new_fullname = *smb_fname_rel;
+
+               DBG_DEBUG("rel->base_name changed from %s to %s\n",
+                         orig_rel_base_name,
+                         smb_fname_rel->base_name);
+
+               new_fullname.base_name = full_path_from_dirfsp_at_basename(
+                       talloc_tos(), dirfsp, new_fullname.base_name);
+               if (new_fullname.base_name == NULL) {
+                       fd_close(fsp);
+                       file_free(NULL, fsp);
+                       return NT_STATUS_NO_MEMORY;
+               }
+
+               status = fsp_set_smb_fname(fsp, &new_fullname);
+               if (!NT_STATUS_IS_OK(status)) {
+                       fd_close(fsp);
+                       file_free(NULL, fsp);
+                       return status;
+               }
+       }
+
+       fsp_set_fd(fsp, fd);
+
+       if (fd >= 0) {
+               ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
+       } else {
+               ret = SMB_VFS_FSTATAT(fsp->conn,
+                                     dirfsp,
+                                     smb_fname_rel,
+                                     &fsp->fsp_name->st,
+                                     AT_SYMLINK_NOFOLLOW);
+       }
+       if (ret == -1) {
+               status = map_nt_error_from_unix(errno);
+               DBG_DEBUG("SMB_VFS_%sSTAT(%s/%s) failed: %s\n",
+                         (fd >= 0) ? "F" : "",
+                         dirfsp->fsp_name->base_name,
+                         smb_fname_rel->base_name,
+                         nt_errstr(status));
+               fd_close(fsp);
+               file_free(NULL, fsp);
+               return status;
+       }
+
+       fsp->fsp_flags.is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
+       fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+
+       smb_fname_rel->st = fsp->fsp_name->st;
+
+       status = fsp_smb_fname_link(fsp,
+                                   &smb_fname_rel->fsp_link,
+                                   &smb_fname_rel->fsp);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_DEBUG("fsp_smb_fname_link() failed: %s\n",
+                         nt_errstr(status));
+               fd_close(fsp);
+               file_free(NULL, fsp);
+               return status;
+       }
+
+       DBG_DEBUG("fsp [%s]: OK, fd=%d\n", fsp_str_dbg(fsp), fd);
+
+       talloc_set_destructor(smb_fname_rel, smb_fname_fsp_destructor);
+       return NT_STATUS_OK;
+}
+
 void smb_fname_fsp_unlink(struct smb_filename *smb_fname)
 {
        talloc_set_destructor(smb_fname, NULL);