smbd: Use dos_mode_debug_print in get_ea_dos_attribute
[kai/samba-autobuild/.git] / source3 / smbd / dosmode.c
index 2a6b3fe9607c1ceea1460c3b9b85ac8373e41f13..b2476ce262eec86952bca1ac05e0ad3cd4f04ac5 100644 (file)
 #include "smbd/smbd.h"
 #include "lib/param/loadparm.h"
 
-static void dos_mode_debug_print(uint32_t mode)
+static NTSTATUS get_file_handle_for_metadata(connection_struct *conn,
+                               struct smb_filename *smb_fname,
+                               files_struct **ret_fsp,
+                               bool *need_close);
+
+static void dos_mode_debug_print(const char *func, uint32_t mode)
 {
-       DEBUG(8,("dos_mode returning "));
+       fstring modestr;
+
+       modestr[0] = '\0';
 
        if (mode & FILE_ATTRIBUTE_HIDDEN) {
-               DEBUG(8, ("h"));
+               fstrcat(modestr, "h");
        }
        if (mode & FILE_ATTRIBUTE_READONLY) {
-               DEBUG(8, ("r"));
+               fstrcat(modestr, "r");
        }
        if (mode & FILE_ATTRIBUTE_SYSTEM) {
-               DEBUG(8, ("s"));
+               fstrcat(modestr, "s");
        }
        if (mode & FILE_ATTRIBUTE_DIRECTORY) {
-               DEBUG(8, ("d"));
+               fstrcat(modestr, "d");
        }
        if (mode & FILE_ATTRIBUTE_ARCHIVE) {
-               DEBUG(8, ("a"));
+               fstrcat(modestr, "a");
        }
        if (mode & FILE_ATTRIBUTE_SPARSE) {
-               DEBUG(8, ("[sparse]"));
+               fstrcat(modestr, "[sparse]");
        }
        if (mode & FILE_ATTRIBUTE_OFFLINE) {
-               DEBUG(8, ("[offline]"));
+               fstrcat(modestr, "[offline]");
        }
        if (mode & FILE_ATTRIBUTE_COMPRESSED) {
-               DEBUG(8, ("[compressed]"));
+               fstrcat(modestr, "[compressed]");
        }
 
-       DEBUG(8,("\n"));
+       DBG_INFO("%s returning %s\n", func, modestr);
 }
 
 static uint32_t filter_mode_by_protocol(uint32_t mode)
@@ -116,7 +123,7 @@ mode_t unix_mode(connection_struct *conn, int dosmode,
                result &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
        }
 
-       if ((inherit_from_dir != NULL) && lp_inherit_perms(SNUM(conn))) {
+       if ((inherit_from_dir != NULL) && lp_inherit_permissions(SNUM(conn))) {
                struct smb_filename *smb_fname_parent;
 
                DEBUG(2, ("unix_mode(%s) inheriting from %s\n",
@@ -187,8 +194,9 @@ mode_t unix_mode(connection_struct *conn, int dosmode,
                }
        }
 
-       DEBUG(3,("unix_mode(%s) returning 0%o\n", smb_fname_str_dbg(smb_fname),
-                (int)result));
+       DBG_INFO("unix_mode(%s) returning 0%o\n",
+                smb_fname_str_dbg(smb_fname), (int)result);
+
        return(result);
 }
 
@@ -196,7 +204,7 @@ mode_t unix_mode(connection_struct *conn, int dosmode,
  Change a unix mode to a dos mode.
 ****************************************************************************/
 
-static uint32 dos_mode_from_sbuf(connection_struct *conn,
+static uint32_t dos_mode_from_sbuf(connection_struct *conn,
                                 const struct smb_filename *smb_fname)
 {
        int result = 0;
@@ -234,15 +242,8 @@ static uint32 dos_mode_from_sbuf(connection_struct *conn,
 
        result |= set_link_read_only_flag(&smb_fname->st);
 
-       DEBUG(8,("dos_mode_from_sbuf returning "));
+       dos_mode_debug_print(__func__, result);
 
-       if (result & FILE_ATTRIBUTE_HIDDEN) DEBUG(8, ("h"));
-       if (result & FILE_ATTRIBUTE_READONLY ) DEBUG(8, ("r"));
-       if (result & FILE_ATTRIBUTE_SYSTEM) DEBUG(8, ("s"));
-       if (result & FILE_ATTRIBUTE_DIRECTORY   ) DEBUG(8, ("d"));
-       if (result & FILE_ATTRIBUTE_ARCHIVE  ) DEBUG(8, ("a"));
-
-       DEBUG(8,("\n"));
        return result;
 }
 
@@ -253,7 +254,7 @@ static uint32 dos_mode_from_sbuf(connection_struct *conn,
 
 static bool get_ea_dos_attribute(connection_struct *conn,
                                 struct smb_filename *smb_fname,
-                                uint32 *pattr)
+                                uint32_t *pattr)
 {
        struct xattr_DOSATTRIB dosattrib;
        enum ndr_err_code ndr_err;
@@ -314,7 +315,7 @@ static bool get_ea_dos_attribute(connection_struct *conn,
                        if (!null_nttime(dosattrib.info.info1.create_time)) {
                                struct timespec create_time =
                                        nt_time_to_unix_timespec(
-                                               &dosattrib.info.info1.create_time);
+                                               dosattrib.info.info1.create_time);
 
                                update_stat_ex_create_time(&smb_fname->st,
                                                        create_time);
@@ -336,7 +337,7 @@ static bool get_ea_dos_attribute(connection_struct *conn,
                                        !null_nttime(dosattrib.info.info3.create_time)) {
                                struct timespec create_time =
                                        nt_time_to_unix_timespec(
-                                               &dosattrib.info.info3.create_time);
+                                               dosattrib.info.info3.create_time);
 
                                update_stat_ex_create_time(&smb_fname->st,
                                                        create_time);
@@ -359,17 +360,9 @@ static bool get_ea_dos_attribute(connection_struct *conn,
                dosattr |= FILE_ATTRIBUTE_DIRECTORY;
        }
        /* FILE_ATTRIBUTE_SPARSE is valid on get but not on set. */
-       *pattr = (uint32)(dosattr & (SAMBA_ATTRIBUTES_MASK|FILE_ATTRIBUTE_SPARSE));
-
-       DEBUG(8,("get_ea_dos_attribute returning (0x%x)", dosattr));
-
-       if (dosattr & FILE_ATTRIBUTE_HIDDEN) DEBUG(8, ("h"));
-       if (dosattr & FILE_ATTRIBUTE_READONLY ) DEBUG(8, ("r"));
-       if (dosattr & FILE_ATTRIBUTE_SYSTEM) DEBUG(8, ("s"));
-       if (dosattr & FILE_ATTRIBUTE_DIRECTORY   ) DEBUG(8, ("d"));
-       if (dosattr & FILE_ATTRIBUTE_ARCHIVE  ) DEBUG(8, ("a"));
+       *pattr = (uint32_t)(dosattr & (SAMBA_ATTRIBUTES_MASK|FILE_ATTRIBUTE_SPARSE));
 
-       DEBUG(8,("\n"));
+       dos_mode_debug_print(__func__, *pattr);
 
        return True;
 }
@@ -381,7 +374,7 @@ static bool get_ea_dos_attribute(connection_struct *conn,
 
 static bool set_ea_dos_attribute(connection_struct *conn,
                                 struct smb_filename *smb_fname,
-                                uint32 dosmode)
+                                uint32_t dosmode)
 {
        struct xattr_DOSATTRIB dosattrib;
        enum ndr_err_code ndr_err;
@@ -394,7 +387,7 @@ static bool set_ea_dos_attribute(connection_struct *conn,
        dosattrib.info.info3.valid_flags = XATTR_DOSINFO_ATTRIB|
                                        XATTR_DOSINFO_CREATE_TIME;
        dosattrib.info.info3.attrib = dosmode;
-       unix_timespec_to_nt_time(&dosattrib.info.info3.create_time,
+       dosattrib.info.info3.create_time = unix_timespec_to_nt_time(
                                smb_fname->st.st_ex_btime);
 
        DEBUG(10,("set_ea_dos_attributes: set attribute 0x%x, btime = %s on file %s\n",
@@ -420,6 +413,7 @@ static bool set_ea_dos_attribute(connection_struct *conn,
                             SAMBA_XATTR_DOS_ATTRIB, blob.data, blob.length,
                             0) == -1) {
                bool ret = false;
+               bool need_close = false;
                files_struct *fsp = NULL;
 
                if((errno != EPERM) && (errno != EACCES)) {
@@ -451,14 +445,17 @@ static bool set_ea_dos_attribute(connection_struct *conn,
                }
 
                /*
-                * We need to open the file with write access whilst
-                * still in our current user context. This ensures we
-                * are not violating security in doing the setxattr.
+                * We need to get an open file handle to do the
+                * metadata operation under root.
                 */
 
-               if (!NT_STATUS_IS_OK(open_file_fchmod(conn, smb_fname,
-                                                     &fsp)))
+               if (!NT_STATUS_IS_OK(get_file_handle_for_metadata(conn,
+                                               smb_fname,
+                                               &fsp,
+                                               &need_close))) {
                        return false;
+               }
+
                become_root();
                if (SMB_VFS_FSETXATTR(fsp,
                                     SAMBA_XATTR_DOS_ATTRIB, blob.data,
@@ -466,7 +463,9 @@ static bool set_ea_dos_attribute(connection_struct *conn,
                        ret = true;
                }
                unbecome_root();
-               close_file(NULL, fsp, NORMAL_CLOSE);
+               if (need_close) {
+                       close_file(NULL, fsp, NORMAL_CLOSE);
+               }
                return ret;
        }
        DEBUG(10,("set_ea_dos_attribute: set EA 0x%x on file %s\n",
@@ -479,10 +478,10 @@ static bool set_ea_dos_attribute(connection_struct *conn,
  Change a unix mode to a dos mode for an ms dfs link.
 ****************************************************************************/
 
-uint32 dos_mode_msdfs(connection_struct *conn,
+uint32_t dos_mode_msdfs(connection_struct *conn,
                      const struct smb_filename *smb_fname)
 {
-       uint32 result = 0;
+       uint32_t result = 0;
 
        DEBUG(8,("dos_mode_msdfs: %s\n", smb_fname_str_dbg(smb_fname)));
 
@@ -527,129 +526,11 @@ uint32 dos_mode_msdfs(connection_struct *conn,
         */
        result |= FILE_ATTRIBUTE_REPARSE_POINT;
 
-       DEBUG(8,("dos_mode_msdfs returning "));
-
-       if (result & FILE_ATTRIBUTE_HIDDEN) DEBUG(8, ("h"));
-       if (result & FILE_ATTRIBUTE_READONLY ) DEBUG(8, ("r"));
-       if (result & FILE_ATTRIBUTE_SYSTEM) DEBUG(8, ("s"));
-       if (result & FILE_ATTRIBUTE_DIRECTORY   ) DEBUG(8, ("d"));
-       if (result & FILE_ATTRIBUTE_ARCHIVE  ) DEBUG(8, ("a"));
-       if (result & FILE_ATTRIBUTE_SPARSE ) DEBUG(8, ("[sparse]"));
-
-       DEBUG(8,("\n"));
+       dos_mode_debug_print(__func__, result);
 
        return(result);
 }
 
-#ifdef HAVE_STAT_DOS_FLAGS
-/****************************************************************************
- Convert dos attributes (FILE_ATTRIBUTE_*) to dos stat flags (UF_*)
-****************************************************************************/
-
-int dos_attributes_to_stat_dos_flags(uint32_t dosmode)
-{
-       uint32_t dos_stat_flags = 0;
-
-       if (dosmode & FILE_ATTRIBUTE_ARCHIVE)
-               dos_stat_flags |= UF_DOS_ARCHIVE;
-       if (dosmode & FILE_ATTRIBUTE_HIDDEN)
-               dos_stat_flags |= UF_DOS_HIDDEN;
-       if (dosmode & FILE_ATTRIBUTE_READONLY)
-               dos_stat_flags |= UF_DOS_RO;
-       if (dosmode & FILE_ATTRIBUTE_SYSTEM)
-               dos_stat_flags |= UF_DOS_SYSTEM;
-       if (dosmode & FILE_ATTRIBUTE_NONINDEXED)
-               dos_stat_flags |= UF_DOS_NOINDEX;
-
-       return dos_stat_flags;
-}
-
-/****************************************************************************
- Gets DOS attributes, accessed via st_ex_flags in the stat struct.
-****************************************************************************/
-
-static bool get_stat_dos_flags(connection_struct *conn,
-                              const struct smb_filename *smb_fname,
-                              uint32_t *dosmode)
-{
-       SMB_ASSERT(VALID_STAT(smb_fname->st));
-       SMB_ASSERT(dosmode);
-
-       if (!lp_store_dos_attributes(SNUM(conn))) {
-               return false;
-       }
-
-       DEBUG(5, ("Getting stat dos attributes for %s.\n",
-                 smb_fname_str_dbg(smb_fname)));
-
-       if (smb_fname->st.st_ex_flags & UF_DOS_ARCHIVE)
-               *dosmode |= FILE_ATTRIBUTE_ARCHIVE;
-       if (smb_fname->st.st_ex_flags & UF_DOS_HIDDEN)
-               *dosmode |= FILE_ATTRIBUTE_HIDDEN;
-       if (smb_fname->st.st_ex_flags & UF_DOS_RO)
-               *dosmode |= FILE_ATTRIBUTE_READONLY;
-       if (smb_fname->st.st_ex_flags & UF_DOS_SYSTEM)
-               *dosmode |= FILE_ATTRIBUTE_SYSTEM;
-       if (smb_fname->st.st_ex_flags & UF_DOS_NOINDEX)
-               *dosmode |= FILE_ATTRIBUTE_NONINDEXED;
-       if (smb_fname->st.st_ex_flags & FILE_ATTRIBUTE_SPARSE)
-               *dosmode |= FILE_ATTRIBUTE_SPARSE;
-       if (S_ISDIR(smb_fname->st.st_ex_mode))
-               *dosmode |= FILE_ATTRIBUTE_DIRECTORY;
-
-       *dosmode |= set_link_read_only_flag(&smb_fname->st);
-
-       return true;
-}
-
-/****************************************************************************
- Sets DOS attributes, stored in st_ex_flags of the inode.
-****************************************************************************/
-
-static bool set_stat_dos_flags(connection_struct *conn,
-                              const struct smb_filename *smb_fname,
-                              uint32_t dosmode,
-                              bool *attributes_changed)
-{
-       uint32_t new_flags = 0;
-       int error = 0;
-
-       SMB_ASSERT(VALID_STAT(smb_fname->st));
-       SMB_ASSERT(attributes_changed);
-
-       *attributes_changed = false;
-
-       if (!lp_store_dos_attributes(SNUM(conn))) {
-               return false;
-       }
-
-       DEBUG(5, ("Setting stat dos attributes for %s.\n",
-                 smb_fname_str_dbg(smb_fname)));
-
-       new_flags = (smb_fname->st.st_ex_flags & ~UF_DOS_FLAGS) |
-                    dos_attributes_to_stat_dos_flags(dosmode);
-
-       /* Return early if no flags changed. */
-       if (new_flags == smb_fname->st.st_ex_flags)
-               return true;
-
-       DEBUG(5, ("Setting stat dos attributes=0x%x, prev=0x%x\n", new_flags,
-                 smb_fname->st.st_ex_flags));
-
-       /* Set new flags with chflags. */
-       error = SMB_VFS_CHFLAGS(conn, smb_fname->base_name, new_flags);
-       if (error) {
-               DEBUG(0, ("Failed setting new stat dos attributes (0x%x) on "
-                         "file %s! errno=%d\n", new_flags,
-                         smb_fname_str_dbg(smb_fname), errno));
-               return false;
-       }
-
-       *attributes_changed = true;
-       return true;
-}
-#endif /* HAVE_STAT_DOS_FLAGS */
-
 /*
  * check whether a file or directory is flagged as compressed.
  */
@@ -690,10 +571,10 @@ err_out:
  if "store dos attributes" is true.
 ****************************************************************************/
 
-uint32 dos_mode(connection_struct *conn, struct smb_filename *smb_fname)
+uint32_t dos_mode(connection_struct *conn, struct smb_filename *smb_fname)
 {
-       uint32 result = 0;
-       bool offline, used_stat_dos_flags = false;
+       uint32_t result = 0;
+       bool offline;
 
        DEBUG(8,("dos_mode: %s\n", smb_fname_str_dbg(smb_fname)));
 
@@ -718,14 +599,9 @@ uint32 dos_mode(connection_struct *conn, struct smb_filename *smb_fname)
                }
        }
 
-#ifdef HAVE_STAT_DOS_FLAGS
-       used_stat_dos_flags = get_stat_dos_flags(conn, smb_fname, &result);
-#endif
-       if (!used_stat_dos_flags) {
-               /* Get the DOS attributes from an EA by preference. */
-               if (!get_ea_dos_attribute(conn, smb_fname, &result)) {
-                       result |= dos_mode_from_sbuf(conn, smb_fname);
-               }
+       /* Get the DOS attributes from an EA by preference. */
+       if (!get_ea_dos_attribute(conn, smb_fname, &result)) {
+               result |= dos_mode_from_sbuf(conn, smb_fname);
        }
 
        offline = SMB_VFS_IS_OFFLINE(conn, smb_fname, &smb_fname->st);
@@ -755,7 +631,7 @@ uint32 dos_mode(connection_struct *conn, struct smb_filename *smb_fname)
 
        result = filter_mode_by_protocol(result);
 
-       dos_mode_debug_print(result);
+       dos_mode_debug_print(__func__, result);
 
        return result;
 }
@@ -768,7 +644,7 @@ uint32 dos_mode(connection_struct *conn, struct smb_filename *smb_fname)
 ********************************************************************/
 
 int file_set_dosmode(connection_struct *conn, struct smb_filename *smb_fname,
-                    uint32 dosmode, const char *parent_dir, bool newfile)
+                    uint32_t dosmode, const char *parent_dir, bool newfile)
 {
        int mask=0;
        mode_t tmp;
@@ -777,6 +653,8 @@ int file_set_dosmode(connection_struct *conn, struct smb_filename *smb_fname,
        uint32_t old_mode;
        struct timespec new_create_timespec;
        files_struct *fsp = NULL;
+       bool need_close = false;
+       NTSTATUS status;
 
        if (!CAN_WRITE(conn)) {
                errno = EROFS;
@@ -827,23 +705,6 @@ int file_set_dosmode(connection_struct *conn, struct smb_filename *smb_fname,
 
        smb_fname->st.st_ex_btime = new_create_timespec;
 
-#ifdef HAVE_STAT_DOS_FLAGS
-       {
-               bool attributes_changed;
-
-               if (set_stat_dos_flags(conn, smb_fname, dosmode,
-                                      &attributes_changed))
-               {
-                       if (!newfile && attributes_changed) {
-                               notify_fname(conn, NOTIFY_ACTION_MODIFIED,
-                                   FILE_NOTIFY_CHANGE_ATTRIBUTES,
-                                   smb_fname->base_name);
-                       }
-                       smb_fname->st.st_ex_mode = unixmode;
-                       return 0;
-               }
-       }
-#endif
        /* Store the DOS attributes in an EA by preference. */
        if (lp_store_dos_attributes(SNUM(conn))) {
                /*
@@ -945,17 +806,25 @@ int file_set_dosmode(connection_struct *conn, struct smb_filename *smb_fname,
        }
 
        /*
-        * We need to open the file with write access whilst
-        * still in our current user context. This ensures we
-        * are not violating security in doing the fchmod.
+        * We need to get an open file handle to do the
+        * metadata operation under root.
         */
-       if (!NT_STATUS_IS_OK(open_file_fchmod(conn, smb_fname,
-                            &fsp)))
+
+       status = get_file_handle_for_metadata(conn,
+                                             smb_fname,
+                                             &fsp,
+                                             &need_close);
+       if (!NT_STATUS_IS_OK(status)) {
+               errno = map_errno_from_nt_status(status);
                return -1;
+       }
+
        become_root();
        ret = SMB_VFS_FCHMOD(fsp, unixmode);
        unbecome_root();
-       close_file(NULL, fsp, NORMAL_CLOSE);
+       if (need_close) {
+               close_file(NULL, fsp, NORMAL_CLOSE);
+       }
        if (!newfile) {
                notify_fname(conn, NOTIFY_ACTION_MODIFIED,
                             FILE_NOTIFY_CHANGE_ATTRIBUTES,
@@ -986,8 +855,13 @@ NTSTATUS file_set_sparse(connection_struct *conn,
                return NT_STATUS_MEDIA_WRITE_PROTECTED;
        }
 
-       if (!(fsp->access_mask & FILE_WRITE_DATA) &&
-                       !(fsp->access_mask & FILE_WRITE_ATTRIBUTES)) {
+       /*
+        * Windows Server 2008 & 2012 permit FSCTL_SET_SPARSE if any of the
+        * following access flags are granted.
+        */
+       if ((fsp->access_mask & (FILE_WRITE_DATA
+                               | FILE_WRITE_ATTRIBUTES
+                               | SEC_FILE_APPEND_DATA)) == 0) {
                DEBUG(9,("file_set_sparse: fname[%s] set[%u] "
                        "access_mask[0x%08X] - access denied\n",
                        smb_fname_str_dbg(fsp->fsp_name),
@@ -996,6 +870,19 @@ NTSTATUS file_set_sparse(connection_struct *conn,
                return NT_STATUS_ACCESS_DENIED;
        }
 
+       if (fsp->is_directory) {
+               DEBUG(9, ("invalid attempt to %s sparse flag on dir %s\n",
+                         (sparse ? "set" : "clear"),
+                         smb_fname_str_dbg(fsp->fsp_name)));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (IS_IPC(conn) || IS_PRINT(conn)) {
+               DEBUG(9, ("attempt to %s sparse flag over invalid conn\n",
+                         (sparse ? "set" : "clear")));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
        DEBUG(10,("file_set_sparse: setting sparse bit %u on file %s\n",
                  sparse, smb_fname_str_dbg(fsp->fsp_name)));
 
@@ -1191,3 +1078,64 @@ struct timespec get_change_timespec(connection_struct *conn,
 {
        return smb_fname->st.st_ex_mtime;
 }
+
+/****************************************************************************
+ Get a real open file handle we can do meta-data operations on. As it's
+ going to be used under root access only on meta-data we should look for
+ any existing open file handle first, and use that in preference (also to
+ avoid kernel self-oplock breaks). If not use an INTERNAL_OPEN_ONLY handle.
+****************************************************************************/
+
+static NTSTATUS get_file_handle_for_metadata(connection_struct *conn,
+                               struct smb_filename *smb_fname,
+                               files_struct **ret_fsp,
+                               bool *need_close)
+{
+       NTSTATUS status;
+       files_struct *fsp;
+       struct file_id file_id;
+
+       *need_close = false;
+
+       if (!VALID_STAT(smb_fname->st)) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+
+       for(fsp = file_find_di_first(conn->sconn, file_id);
+                       fsp;
+                       fsp = file_find_di_next(fsp)) {
+               if (fsp->fh->fd != -1) {
+                       *ret_fsp = fsp;
+                       return NT_STATUS_OK;
+               }
+       }
+
+       /* Opens an INTERNAL_OPEN_ONLY write handle. */
+       status = SMB_VFS_CREATE_FILE(
+               conn,                                   /* conn */
+               NULL,                                   /* req */
+               0,                                      /* root_dir_fid */
+               smb_fname,                              /* fname */
+               FILE_WRITE_DATA,                        /* access_mask */
+               (FILE_SHARE_READ | FILE_SHARE_WRITE |   /* share_access */
+                       FILE_SHARE_DELETE),
+               FILE_OPEN,                              /* create_disposition*/
+               0,                                      /* create_options */
+               0,                                      /* file_attributes */
+               INTERNAL_OPEN_ONLY,                     /* oplock_request */
+               NULL,                                   /* lease */
+                0,                                      /* allocation_size */
+               0,                                      /* private_flags */
+               NULL,                                   /* sd */
+               NULL,                                   /* ea_list */
+               ret_fsp,                                /* result */
+               NULL,                                   /* pinfo */
+               NULL, NULL);                            /* create context */
+
+       if (NT_STATUS_IS_OK(status)) {
+               *need_close = true;
+       }
+       return status;
+}