s3/smbd: Fix error code for unsupported SET_INFO requests
[sfrench/samba-autobuild/.git] / source3 / smbd / file_access.c
index a58bcdd8915c361210e1d2f595090998b304a5ac..66c9ed3add7c5d32b65c051c770c102ef19aacf2 100644 (file)
 */
 
 #include "includes.h"
-
-extern struct current_user current_user;
+#include "system/filesys.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "smbd/smbd.h"
 
 #undef  DBGC_CLASS
 #define DBGC_CLASS DBGC_ACLS
 
-/****************************************************************************
- Helper function that gets a security descriptor by connection and
- file name.
- NOTE: This is transitional, in the sense that SMB_VFS_GET_NT_ACL really
- should *not* get a files_struct pointer but a connection_struct ptr
- (automatic by the vfs handle) and the file name and _use_ that!
-****************************************************************************/
-static NTSTATUS conn_get_nt_acl(TALLOC_CTX *mem_ctx,
-                               struct connection_struct *conn,
-                               const char *fname,
-                               SMB_STRUCT_STAT *psbuf,
-                               struct security_descriptor **psd)
-{
-       NTSTATUS status;
-       struct files_struct *fsp = NULL;
-       struct security_descriptor *secdesc = NULL;
-
-       if (!VALID_STAT(*psbuf)) {
-               if (SMB_VFS_STAT(conn, fname, psbuf) != 0) {
-                       return map_nt_error_from_unix(errno);
-               }
-       }
-
-       /* fake a files_struct ptr: */
-
-       if (S_ISDIR(psbuf->st_mode)) {
-               status = open_directory(conn, NULL, fname, psbuf,
-                                       READ_CONTROL_ACCESS,
-                                       FILE_SHARE_READ|FILE_SHARE_WRITE,
-                                       FILE_OPEN,
-                                       0,
-                                       FILE_ATTRIBUTE_DIRECTORY,
-                                       NULL, &fsp);
-       }
-       else {
-               status = open_file_stat(conn, NULL, fname, psbuf, &fsp);
-       }
-
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(3, ("Unable to open file %s: %s\n", fname,
-                         nt_errstr(status)));
-               return status;
-       }
-
-       status = SMB_VFS_GET_NT_ACL(fsp, fname,
-                                   (OWNER_SECURITY_INFORMATION |
-                                    GROUP_SECURITY_INFORMATION |
-                                    DACL_SECURITY_INFORMATION),
-                                   &secdesc);
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(5, ("Unable to get NT ACL for file %s\n", fname));
-               goto done;
-       }
-
-       *psd = talloc_move(mem_ctx, &secdesc);
-
-done:
-       close_file(fsp, NORMAL_CLOSE);
-       return status;
-}
-
-static bool can_access_file_acl(struct connection_struct *conn,
-                               const char * fname, SMB_STRUCT_STAT *psbuf,
-                               uint32_t access_mask)
-{
-       bool result;
-       NTSTATUS status;
-       uint32_t access_granted;
-       struct security_descriptor *secdesc = NULL;
-
-       status = conn_get_nt_acl(talloc_tos(), conn, fname, psbuf, &secdesc);
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(5, ("Could not get acl: %s\n", nt_errstr(status)));
-               return false;
-       }
-
-       result = se_access_check(secdesc, current_user.nt_user_token,
-                                access_mask, &access_granted, &status);
-       TALLOC_FREE(secdesc);
-       return result;
-}
-
 /****************************************************************************
  Actually emulate the in-kernel access checking for delete access. We need
  this to successfully return ACCESS_DENIED on a file open for delete access.
 ****************************************************************************/
 
-bool can_delete_file_in_directory(connection_struct *conn, const char *fname)
+bool can_delete_file_in_directory(connection_struct *conn,
+                                 const struct smb_filename *smb_fname)
 {
-       SMB_STRUCT_STAT sbuf;
        TALLOC_CTX *ctx = talloc_tos();
        char *dname = NULL;
+       struct smb_filename *smb_fname_parent;
+       bool ret;
 
        if (!CAN_WRITE(conn)) {
                return False;
        }
 
+       if (!lp_acl_check_permissions(SNUM(conn))) {
+               /* This option means don't check. */
+               return true;
+       }
+
        /* Get the parent directory permission mask and owners. */
-       if (!parent_dirname_talloc(ctx,
-                               fname,
-                               &dname,
-                               NULL)) {
+       if (!parent_dirname(ctx, smb_fname->base_name, &dname, NULL)) {
                return False;
        }
-       if(SMB_VFS_STAT(conn, dname, &sbuf) != 0) {
-               return False;
+
+       smb_fname_parent = synthetic_smb_fname(ctx,
+                               dname,
+                               NULL,
+                               NULL,
+                               smb_fname->flags);
+       if (smb_fname_parent == NULL) {
+               ret = false;
+               goto out;
+       }
+
+       if(SMB_VFS_STAT(conn, smb_fname_parent) != 0) {
+               ret = false;
+               goto out;
        }
 
        /* fast paths first */
 
-       if (!S_ISDIR(sbuf.st_mode)) {
-               return False;
+       if (!S_ISDIR(smb_fname_parent->st.st_ex_mode)) {
+               ret = false;
+               goto out;
        }
-       if (current_user.ut.uid == 0 || conn->admin_user) {
+       if (get_current_uid(conn) == (uid_t)0) {
                /* I'm sorry sir, I didn't know you were root... */
-               return True;
-       }
-
-       /* Check primary owner write access. */
-       if (current_user.ut.uid == sbuf.st_uid) {
-               return (sbuf.st_mode & S_IWUSR) ? True : False;
+               ret = true;
+               goto out;
        }
 
 #ifdef S_ISVTX
-       /* sticky bit means delete only by owner or root. */
-       if (sbuf.st_mode & S_ISVTX) {
-               SMB_STRUCT_STAT sbuf_file;
-               if(SMB_VFS_STAT(conn, fname, &sbuf_file) != 0) {
-                       if (errno == ENOENT) {
-                               /* If the file doesn't already exist then
-                                * yes we'll be able to delete it. */
-                               return True;
-                       }
-                       return False;
+       /* sticky bit means delete only by owner of file or by root or
+        * by owner of directory. */
+       if (smb_fname_parent->st.st_ex_mode & S_ISVTX) {
+               if (!VALID_STAT(smb_fname->st)) {
+                       /* If the file doesn't already exist then
+                        * yes we'll be able to delete it. */
+                       ret = true;
+                       goto out;
                }
+
                /*
                 * Patch from SATOH Fumiyasu <fumiyas@miraclelinux.com>
                 * for bug #3348. Don't assume owning sticky bit
                 * directory means write access allowed.
+                * Fail to delete if we're not the owner of the file,
+                * or the owner of the directory as we have no possible
+                * chance of deleting. Otherwise, go on and check the ACL.
                 */
-               if (current_user.ut.uid != sbuf_file.st_uid) {
-                       return False;
+               if ((get_current_uid(conn) !=
+                       smb_fname_parent->st.st_ex_uid) &&
+                   (get_current_uid(conn) != smb_fname->st.st_ex_uid)) {
+                       DEBUG(10,("can_delete_file_in_directory: not "
+                                 "owner of file %s or directory %s",
+                                 smb_fname_str_dbg(smb_fname),
+                                 smb_fname_str_dbg(smb_fname_parent)));
+                       ret = false;
+                       goto out;
                }
        }
 #endif
 
        /* now for ACL checks */
 
-       return can_access_file_acl(conn, dname, &sbuf, FILE_WRITE_DATA);
+       /*
+        * There's two ways to get the permission to delete a file: First by
+        * having the DELETE bit on the file itself and second if that does
+        * not help, by the DELETE_CHILD bit on the containing directory.
+        *
+        * Here we only check the directory permissions, we will
+        * check the file DELETE permission separately.
+        */
+
+       ret = NT_STATUS_IS_OK(smbd_check_access_rights(conn,
+                               smb_fname_parent,
+                               false,
+                               FILE_DELETE_CHILD));
+ out:
+       TALLOC_FREE(dname);
+       TALLOC_FREE(smb_fname_parent);
+       return ret;
 }
 
 /****************************************************************************
- Actually emulate the in-kernel access checking for read/write access. We need
- this to successfully check for ability to write for dos filetimes.
- Note this doesn't take into account share write permissions.
+ Userspace check for write access.
 ****************************************************************************/
 
-bool can_access_file(connection_struct *conn, const char *fname, SMB_STRUCT_STAT *psbuf, uint32 access_mask)
+bool can_write_to_file(connection_struct *conn,
+                      const struct smb_filename *smb_fname)
 {
-       if (!(access_mask & (FILE_READ_DATA|FILE_WRITE_DATA))) {
-               return False;
-       }
-       access_mask &= (FILE_READ_DATA|FILE_WRITE_DATA);
-
-       /* some fast paths first */
+       return NT_STATUS_IS_OK(smbd_check_access_rights(conn,
+                               smb_fname,
+                               false,
+                               FILE_WRITE_DATA));
+}
 
-       DEBUG(10,("can_access_file: requesting 0x%x on file %s\n",
-               (unsigned int)access_mask, fname ));
+/****************************************************************************
+ Check for an existing default Windows ACL on a directory.
+****************************************************************************/
 
-       if (current_user.ut.uid == 0 || conn->admin_user) {
-               /* I'm sorry sir, I didn't know you were root... */
-               return True;
-       }
+bool directory_has_default_acl(connection_struct *conn, const char *fname)
+{
+       struct security_descriptor *secdesc = NULL;
+       unsigned int i;
+       NTSTATUS status;
+       struct smb_filename *smb_fname = synthetic_smb_fname(talloc_tos(),
+                                               fname,
+                                               NULL,
+                                               NULL,
+                                               0);
 
-       if (!VALID_STAT(*psbuf)) {
-               /* Get the file permission mask and owners. */
-               if(SMB_VFS_STAT(conn, fname, psbuf) != 0) {
-                       return False;
-               }
+       if (smb_fname == NULL) {
+               return false;
        }
 
-       /* Check primary owner access. */
-       if (current_user.ut.uid == psbuf->st_uid) {
-               switch (access_mask) {
-                       case FILE_READ_DATA:
-                               return (psbuf->st_mode & S_IRUSR) ? True : False;
-
-                       case FILE_WRITE_DATA:
-                               return (psbuf->st_mode & S_IWUSR) ? True : False;
+       status = SMB_VFS_GET_NT_ACL(conn, smb_fname,
+                                            SECINFO_DACL, talloc_tos(),
+                                            &secdesc);
 
-                       default: /* FILE_READ_DATA|FILE_WRITE_DATA */
+       if (!NT_STATUS_IS_OK(status) ||
+                       secdesc == NULL ||
+                       secdesc->dacl == NULL) {
+               TALLOC_FREE(secdesc);
+               return false;
+       }
 
-                               if ((psbuf->st_mode & (S_IWUSR|S_IRUSR)) == (S_IWUSR|S_IRUSR)) {
-                                       return True;
-                               } else {
-                                       return False;
-                               }
+       for (i = 0; i < secdesc->dacl->num_aces; i++) {
+               struct security_ace *psa = &secdesc->dacl->aces[i];
+               if (psa->flags & (SEC_ACE_FLAG_OBJECT_INHERIT|
+                               SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+                       TALLOC_FREE(secdesc);
+                       return true;
                }
        }
-
-       /* now for ACL checks */
-
-       return can_access_file_acl(conn, fname, psbuf, access_mask);
+       TALLOC_FREE(secdesc);
+       return false;
 }
 
 /****************************************************************************
- Userspace check for write access.
- Note this doesn't take into account share write permissions.
+ Check if setting delete on close is allowed on this fsp.
 ****************************************************************************/
 
-bool can_write_to_file(connection_struct *conn, const char *fname, SMB_STRUCT_STAT *psbuf)
+NTSTATUS can_set_delete_on_close(files_struct *fsp, uint32_t dosmode)
 {
-       return can_access_file(conn, fname, psbuf, FILE_WRITE_DATA);
-}
+       /*
+        * Only allow delete on close for writable files.
+        */
+
+       if ((dosmode & FILE_ATTRIBUTE_READONLY) &&
+           !lp_delete_readonly(SNUM(fsp->conn))) {
+               DEBUG(10,("can_set_delete_on_close: file %s delete on close "
+                         "flag set but file attribute is readonly.\n",
+                         fsp_str_dbg(fsp)));
+               return NT_STATUS_CANNOT_DELETE;
+       }
+
+       /*
+        * Only allow delete on close for writable shares.
+        */
+
+       if (!CAN_WRITE(fsp->conn)) {
+               DEBUG(10,("can_set_delete_on_close: file %s delete on "
+                         "close flag set but write access denied on share.\n",
+                         fsp_str_dbg(fsp)));
+               return NT_STATUS_ACCESS_DENIED;
+       }
+
+       /*
+        * Only allow delete on close for files/directories opened with delete
+        * intent.
+        */
+
+       if (!(fsp->access_mask & DELETE_ACCESS)) {
+               DEBUG(10,("can_set_delete_on_close: file %s delete on "
+                         "close flag set but delete access denied.\n",
+                         fsp_str_dbg(fsp)));
+               return NT_STATUS_ACCESS_DENIED;
+       }
+
+       /* Don't allow delete on close for non-empty directories. */
+       if (fsp->is_directory) {
+               SMB_ASSERT(!is_ntfs_stream_smb_fname(fsp->fsp_name));
+
+               /* Or the root of a share. */
+               if (ISDOT(fsp->fsp_name->base_name)) {
+                       DEBUG(10,("can_set_delete_on_close: can't set delete on "
+                                 "close for the root of a share.\n"));
+                       return NT_STATUS_ACCESS_DENIED;
+               }
+
+               return can_delete_directory_fsp(fsp);
+       }
 
+       return NT_STATUS_OK;
+}