Fix bug #8370 - vfs_chown_fsp broken -- returns in the wrong directory
[idra/samba.git] / source3 / smbd / vfs.c
index bbe6e7ef1ed9397a697b5a0ea4b79d4f6acdabdc..c6edef282faaa806c349a1163d8edc01bebf15ac 100644 (file)
@@ -29,6 +29,7 @@
 #include "smbd/globals.h"
 #include "memcache.h"
 #include "transfer_file.h"
+#include "ntioctl.h"
 
 #undef DBGC_CLASS
 #define DBGC_CLASS DBGC_VFS
@@ -182,7 +183,7 @@ bool vfs_init_custom(connection_struct *conn, const char *vfs_object)
 
        DEBUGADD(5,("Successfully loaded vfs module [%s] with the new modules system\n", vfs_object));
 
-       handle = TALLOC_ZERO_P(conn, vfs_handle_struct);
+       handle = talloc_zero(conn, vfs_handle_struct);
        if (!handle) {
                DEBUG(0,("TALLOC_ZERO() failed!\n"));
                goto fail;
@@ -728,7 +729,7 @@ const char *vfs_readdirname(connection_struct *conn, void *p,
        if (!p)
                return(NULL);
 
-       ptr = SMB_VFS_READDIR(conn, (DIR *)p, sbuf);
+       ptr = SMB_VFS_READDIR(conn, (SMB_STRUCT_DIR *)p, sbuf);
        if (!ptr)
                return(NULL);
 
@@ -794,7 +795,7 @@ int vfs_ChDir(connection_struct *conn, const char *path)
 
 char *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn)
 {
-        char s[PATH_MAX+1];
+        char *current_dir = NULL;
        char *result = NULL;
        DATA_BLOB cache_value;
        struct file_id key;
@@ -802,8 +803,6 @@ char *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn)
        struct smb_filename *smb_fname_full = NULL;
        NTSTATUS status;
 
-       *s = 0;
-
        if (!lp_getwd_cache()) {
                goto nocache;
        }
@@ -865,7 +864,8 @@ char *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn)
         * systems, or the not quite so bad getwd.
         */
 
-       if (!SMB_VFS_GETWD(conn,s)) {
+       current_dir = SMB_VFS_GETWD(conn);
+       if (current_dir == NULL) {
                DEBUG(0, ("vfs_GetWd: SMB_VFS_GETWD call failed: %s\n",
                          strerror(errno)));
                goto out;
@@ -876,10 +876,11 @@ char *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn)
 
                memcache_add(smbd_memcache(), GETWD_CACHE,
                             data_blob_const(&key, sizeof(key)),
-                            data_blob_const(s, strlen(s)+1));
+                            data_blob_const(current_dir,
+                                               strlen(current_dir)+1));
        }
 
-       result = talloc_strdup(ctx, s);
+       result = talloc_strdup(ctx, current_dir);
        if (result == NULL) {
                errno = ENOMEM;
        }
@@ -887,6 +888,7 @@ char *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn)
  out:
        TALLOC_FREE(smb_fname_dot);
        TALLOC_FREE(smb_fname_full);
+       SAFE_FREE(current_dir);
        return result;
 }
 
@@ -898,7 +900,8 @@ char *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn)
 NTSTATUS check_reduced_name(connection_struct *conn, const char *fname)
 {
        char *resolved_name = NULL;
-       char *p = NULL;
+       bool allow_symlinks = true;
+       bool allow_widelinks = false;
 
        DEBUG(3,("check_reduced_name [%s] [%s]\n", fname, conn->connectpath));
 
@@ -914,28 +917,20 @@ NTSTATUS check_reduced_name(connection_struct *conn, const char *fname)
                        case ENOENT:
                        {
                                TALLOC_CTX *ctx = talloc_tos();
-                               char *tmp_fname = NULL;
-                               char *last_component = NULL;
-                               /* Last component didn't exist. Remove it and try and canonicalise the directory. */
-
-                               tmp_fname = talloc_strdup(ctx, fname);
-                               if (!tmp_fname) {
+                               char *dir_name = NULL;
+                               const char *last_component = NULL;
+                               char *new_name = NULL;
+
+                               /* Last component didn't exist.
+                                  Remove it and try and canonicalise
+                                  the directory name. */
+                               if (!parent_dirname(ctx, fname,
+                                               &dir_name,
+                                               &last_component)) {
                                        return NT_STATUS_NO_MEMORY;
                                }
-                               p = strrchr_m(tmp_fname, '/');
-                               if (p) {
-                                       *p++ = '\0';
-                                       last_component = p;
-                               } else {
-                                       last_component = tmp_fname;
-                                       tmp_fname = talloc_strdup(ctx,
-                                                       ".");
-                                       if (!tmp_fname) {
-                                               return NT_STATUS_NO_MEMORY;
-                                       }
-                               }
 
-                               resolved_name = SMB_VFS_REALPATH(conn,tmp_fname);
+                               resolved_name = SMB_VFS_REALPATH(conn,dir_name);
                                if (!resolved_name) {
                                        NTSTATUS status = map_nt_error_from_unix(errno);
 
@@ -950,18 +945,16 @@ NTSTATUS check_reduced_name(connection_struct *conn, const char *fname)
                                                nt_errstr(status)));
                                        return status;
                                }
-                               tmp_fname = talloc_asprintf(ctx,
+                               new_name = talloc_asprintf(ctx,
                                                "%s/%s",
                                                resolved_name,
                                                last_component);
-                               if (!tmp_fname) {
+                               if (!new_name) {
                                        return NT_STATUS_NO_MEMORY;
                                }
                                SAFE_FREE(resolved_name);
-                               resolved_name = SMB_STRDUP(tmp_fname);
+                               resolved_name = SMB_STRDUP(new_name);
                                if (!resolved_name) {
-                                       DEBUG(0, ("check_reduced_name: malloc "
-                                                 "fail for %s\n", tmp_fname));
                                        return NT_STATUS_NO_MEMORY;
                                }
                                break;
@@ -983,57 +976,65 @@ NTSTATUS check_reduced_name(connection_struct *conn, const char *fname)
                return NT_STATUS_OBJECT_NAME_INVALID;
        }
 
-       /* Check for widelinks allowed. */
-       if (!lp_widelinks(SNUM(conn))) {
-                   const char *conn_rootdir;
-
-                   conn_rootdir = SMB_VFS_CONNECTPATH(conn, fname);
-                   if (conn_rootdir == NULL) {
-                           DEBUG(2, ("check_reduced_name: Could not get "
-                                     "conn_rootdir\n"));
-                           SAFE_FREE(resolved_name);
-                           return NT_STATUS_ACCESS_DENIED;
-                   }
-
-                   if (strncmp(conn_rootdir, resolved_name,
-                               strlen(conn_rootdir)) != 0) {
-                           DEBUG(2, ("check_reduced_name: Bad access "
-                                     "attempt: %s is a symlink outside the "
-                                     "share path\n", fname));
-                           DEBUGADD(2, ("conn_rootdir =%s\n", conn_rootdir));
-                           DEBUGADD(2, ("resolved_name=%s\n", resolved_name));
-                           SAFE_FREE(resolved_name);
-                           return NT_STATUS_ACCESS_DENIED;
-                   }
-       }
-
-        /* Check if we are allowing users to follow symlinks */
-        /* Patch from David Clerc <David.Clerc@cui.unige.ch>
-                University of Geneva */
+       allow_widelinks = lp_widelinks(SNUM(conn));
+       allow_symlinks = lp_symlinks(SNUM(conn));
 
-#ifdef S_ISLNK
-        if (!lp_symlinks(SNUM(conn))) {
-               struct smb_filename *smb_fname = NULL;
-               NTSTATUS status;
+       /* Common widelinks and symlinks checks. */
+       if (!allow_widelinks || !allow_symlinks) {
+               const char *conn_rootdir;
+               size_t rootdir_len;
 
-               status = create_synthetic_smb_fname(talloc_tos(), fname, NULL,
-                                                   NULL, &smb_fname);
-               if (!NT_STATUS_IS_OK(status)) {
+               conn_rootdir = SMB_VFS_CONNECTPATH(conn, fname);
+               if (conn_rootdir == NULL) {
+                       DEBUG(2, ("check_reduced_name: Could not get "
+                               "conn_rootdir\n"));
                        SAFE_FREE(resolved_name);
-                        return status;
+                       return NT_STATUS_ACCESS_DENIED;
                }
 
-               if ( (SMB_VFS_LSTAT(conn, smb_fname) != -1) &&
-                                (S_ISLNK(smb_fname->st.st_ex_mode)) ) {
+               rootdir_len = strlen(conn_rootdir);
+               if (strncmp(conn_rootdir, resolved_name,
+                               rootdir_len) != 0) {
+                       DEBUG(2, ("check_reduced_name: Bad access "
+                               "attempt: %s is a symlink outside the "
+                               "share path\n", fname));
+                       DEBUGADD(2, ("conn_rootdir =%s\n", conn_rootdir));
+                       DEBUGADD(2, ("resolved_name=%s\n", resolved_name));
                        SAFE_FREE(resolved_name);
-                        DEBUG(3,("check_reduced_name: denied: file path name "
-                                "%s is a symlink\n",resolved_name));
-                       TALLOC_FREE(smb_fname);
                        return NT_STATUS_ACCESS_DENIED;
-                }
-               TALLOC_FREE(smb_fname);
-        }
-#endif
+               }
+
+               /* Extra checks if all symlinks are disallowed. */
+               if (!allow_symlinks) {
+                       /* fname can't have changed in resolved_path. */
+                       const char *p = &resolved_name[rootdir_len];
+
+                       /* *p can be '\0' if fname was "." */
+                       if (*p == '\0' && ISDOT(fname)) {
+                               goto out;
+                       }
+
+                       if (*p != '/') {
+                               DEBUG(2, ("check_reduced_name: logic error (%c) "
+                                       "in resolved_name: %s\n",
+                                       *p,
+                                       fname));
+                               SAFE_FREE(resolved_name);
+                               return NT_STATUS_ACCESS_DENIED;
+                       }
+
+                       p++;
+                       if (strcmp(fname, p)!=0) {
+                               DEBUG(2, ("check_reduced_name: Bad access "
+                                       "attempt: %s is a symlink\n",
+                                       fname));
+                               SAFE_FREE(resolved_name);
+                               return NT_STATUS_ACCESS_DENIED;
+                       }
+               }
+       }
+
+  out:
 
        DEBUG(3,("check_reduced_name: %s reduced to %s\n", fname,
                 resolved_name));
@@ -1174,7 +1175,7 @@ int smb_vfs_call_set_quota(struct vfs_handle_struct *handle,
 
 int smb_vfs_call_get_shadow_copy_data(struct vfs_handle_struct *handle,
                                      struct files_struct *fsp,
-                                     SHADOW_COPY_DATA *shadow_copy_data,
+                                     struct shadow_copy_data *shadow_copy_data,
                                      bool labels)
 {
        VFS_FIND(get_shadow_copy_data);
@@ -1272,8 +1273,8 @@ int smb_vfs_call_open(struct vfs_handle_struct *handle,
                      struct smb_filename *smb_fname, struct files_struct *fsp,
                      int flags, mode_t mode)
 {
-       VFS_FIND(open);
-       return handle->fns->open(handle, smb_fname, fsp, flags, mode);
+       VFS_FIND(open_fn);
+       return handle->fns->open_fn(handle, smb_fname, fsp, flags, mode);
 }
 
 NTSTATUS smb_vfs_call_create_file(struct vfs_handle_struct *handle,
@@ -1454,6 +1455,11 @@ int smb_vfs_call_lchown(struct vfs_handle_struct *handle, const char *path,
 NTSTATUS vfs_chown_fsp(files_struct *fsp, uid_t uid, gid_t gid)
 {
        int ret;
+       bool as_root = false;
+       const char *path;
+       char *saved_dir = NULL;
+       char *parent_dir = NULL;
+       NTSTATUS status;
 
        if (fsp->fh->fd != -1) {
                /* Try fchown. */
@@ -1466,19 +1472,84 @@ NTSTATUS vfs_chown_fsp(files_struct *fsp, uid_t uid, gid_t gid)
                }
        }
 
-       if (fsp->posix_open) {
+       as_root = (geteuid() == 0);
+
+       if (as_root) {
+               /*
+                * We are being asked to chown as root. Make
+                * sure we chdir() into the path to pin it,
+                * and always act using lchown to ensure we
+                * don't deref any symbolic links.
+                */
+               const char *final_component = NULL;
+               struct smb_filename local_fname;
+
+               saved_dir = vfs_GetWd(talloc_tos(),fsp->conn);
+               if (!saved_dir) {
+                       status = map_nt_error_from_unix(errno);
+                       DEBUG(0,("vfs_chown_fsp: failed to get "
+                               "current working directory. Error was %s\n",
+                               strerror(errno)));
+                       return status;
+               }
+
+               if (!parent_dirname(talloc_tos(),
+                               fsp->fsp_name->base_name,
+                               &parent_dir,
+                               &final_component)) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+
+               /* cd into the parent dir to pin it. */
+               ret = vfs_ChDir(fsp->conn, parent_dir);
+               if (ret == -1) {
+                       return map_nt_error_from_unix(errno);
+               }
+
+               ZERO_STRUCT(local_fname);
+               local_fname.base_name = discard_const_p(char, final_component);
+
+               /* Must use lstat here. */
+               ret = SMB_VFS_LSTAT(fsp->conn, &local_fname);
+               if (ret == -1) {
+                       status = map_nt_error_from_unix(errno);
+                       goto out;
+               }
+
+               /* Ensure it matches the fsp stat. */
+               if (!check_same_stat(&local_fname.st, &fsp->fsp_name->st)) {
+                        status = NT_STATUS_ACCESS_DENIED;
+                       goto out;
+                }
+                path = final_component;
+        } else {
+                path = fsp->fsp_name->base_name;
+        }
+
+       if (fsp->posix_open || as_root) {
                ret = SMB_VFS_LCHOWN(fsp->conn,
-                       fsp->fsp_name->base_name,
+                       path,
                        uid, gid);
        } else {
                ret = SMB_VFS_CHOWN(fsp->conn,
-                       fsp->fsp_name->base_name,
+                       path,
                        uid, gid);
        }
+
        if (ret == 0) {
-               return NT_STATUS_OK;
+               status = NT_STATUS_OK;
+       } else {
+               status = map_nt_error_from_unix(errno);
+       }
+
+  out:
+
+       if (as_root) {
+               vfs_ChDir(fsp->conn,saved_dir);
+               TALLOC_FREE(saved_dir);
+               TALLOC_FREE(parent_dir);
        }
-       return map_nt_error_from_unix(errno);
+       return status;
 }
 
 int smb_vfs_call_chdir(struct vfs_handle_struct *handle, const char *path)
@@ -1487,10 +1558,10 @@ int smb_vfs_call_chdir(struct vfs_handle_struct *handle, const char *path)
        return handle->fns->chdir(handle, path);
 }
 
-char *smb_vfs_call_getwd(struct vfs_handle_struct *handle, char *buf)
+char *smb_vfs_call_getwd(struct vfs_handle_struct *handle)
 {
        VFS_FIND(getwd);
-       return handle->fns->getwd(handle, buf);
+       return handle->fns->getwd(handle);
 }
 
 int smb_vfs_call_ntimes(struct vfs_handle_struct *handle,