smbd: split out public parse_dos_attribute_blob() from get_ea_dos_attribute()
[kai/samba-autobuild/.git] / source3 / smbd / dosmode.c
index 3c6d47bb6dc820d565372bc2077384f9c9f4df39..ed5ecc9120c50bc968f9604c8082e0cb7a3c8dcf 100644 (file)
@@ -260,16 +260,96 @@ static uint32_t dos_mode_from_sbuf(connection_struct *conn,
  This can also pull the create time into the stat struct inside smb_fname.
 ****************************************************************************/
 
+NTSTATUS parse_dos_attribute_blob(struct smb_filename *smb_fname,
+                                 DATA_BLOB blob,
+                                 uint32_t *pattr)
+{
+       struct xattr_DOSATTRIB dosattrib;
+       enum ndr_err_code ndr_err;
+       uint32_t dosattr;
+
+       ndr_err = ndr_pull_struct_blob(&blob, talloc_tos(), &dosattrib,
+                       (ndr_pull_flags_fn_t)ndr_pull_xattr_DOSATTRIB);
+
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               DBG_WARNING("bad ndr decode "
+                           "from EA on file %s: Error = %s\n",
+                           smb_fname_str_dbg(smb_fname),
+                           ndr_errstr(ndr_err));
+               return ndr_map_error2ntstatus(ndr_err);
+       }
+
+       DBG_DEBUG("%s attr = %s\n",
+                 smb_fname_str_dbg(smb_fname), dosattrib.attrib_hex);
+
+       switch (dosattrib.version) {
+       case 0xFFFF:
+               dosattr = dosattrib.info.compatinfoFFFF.attrib;
+               break;
+       case 1:
+               dosattr = dosattrib.info.info1.attrib;
+               if (!null_nttime(dosattrib.info.info1.create_time)) {
+                       struct timespec create_time =
+                               nt_time_to_unix_timespec(
+                                       dosattrib.info.info1.create_time);
+
+                       update_stat_ex_create_time(&smb_fname->st,
+                                                  create_time);
+
+                       DBG_DEBUG("file %s case 1 set btime %s\n",
+                                 smb_fname_str_dbg(smb_fname),
+                                 time_to_asc(convert_timespec_to_time_t(
+                                                     create_time)));
+               }
+               break;
+       case 2:
+               dosattr = dosattrib.info.oldinfo2.attrib;
+               /* Don't know what flags to check for this case. */
+               break;
+       case 3:
+               dosattr = dosattrib.info.info3.attrib;
+               if ((dosattrib.info.info3.valid_flags & XATTR_DOSINFO_CREATE_TIME) &&
+                   !null_nttime(dosattrib.info.info3.create_time)) {
+                       struct timespec create_time =
+                               nt_time_to_unix_timespec(
+                                       dosattrib.info.info3.create_time);
+
+                       update_stat_ex_create_time(&smb_fname->st,
+                                                  create_time);
+
+                       DBG_DEBUG("file %s case 3 set btime %s\n",
+                                 smb_fname_str_dbg(smb_fname),
+                                 time_to_asc(convert_timespec_to_time_t(
+                                                     create_time)));
+               }
+               break;
+       default:
+               DBG_WARNING("Badly formed DOSATTRIB on file %s - %s\n",
+                           smb_fname_str_dbg(smb_fname), blob.data);
+               /* Should this be INTERNAL_ERROR? */
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+               dosattr |= FILE_ATTRIBUTE_DIRECTORY;
+       }
+
+       /* FILE_ATTRIBUTE_SPARSE is valid on get but not on set. */
+       *pattr |= (uint32_t)(dosattr & (SAMBA_ATTRIBUTES_MASK|FILE_ATTRIBUTE_SPARSE));
+
+       dos_mode_debug_print(__func__, *pattr);
+
+       return NT_STATUS_OK;
+}
+
 NTSTATUS get_ea_dos_attribute(connection_struct *conn,
                              struct smb_filename *smb_fname,
                              uint32_t *pattr)
 {
-       struct xattr_DOSATTRIB dosattrib;
-       enum ndr_err_code ndr_err;
        DATA_BLOB blob;
        ssize_t sizeret;
        fstring attrstr;
-       uint32_t dosattr;
+       NTSTATUS status;
 
        if (!lp_store_dos_attributes(SNUM(conn))) {
                return NT_STATUS_NOT_IMPLEMENTED;
@@ -278,9 +358,45 @@ NTSTATUS get_ea_dos_attribute(connection_struct *conn,
        /* Don't reset pattr to zero as we may already have filename-based attributes we
           need to preserve. */
 
-       sizeret = SMB_VFS_GETXATTR(conn, smb_fname->base_name,
+       sizeret = SMB_VFS_GETXATTR(conn, smb_fname,
                                   SAMBA_XATTR_DOS_ATTRIB, attrstr,
                                   sizeof(attrstr));
+       if (sizeret == -1 && errno == EACCES) {
+               int saved_errno = 0;
+
+               /*
+                * According to MS-FSA 2.1.5.1.2.1 "Algorithm to Check Access to
+                * an Existing File" FILE_LIST_DIRECTORY on a directory implies
+                * FILE_READ_ATTRIBUTES for directory entries. Being able to
+                * stat() a file implies FILE_LIST_DIRECTORY for the directory
+                * containing the file.
+                */
+
+               if (!VALID_STAT(smb_fname->st)) {
+                       /*
+                        * Safety net: dos_mode() already checks this, but as we
+                        * become root based on this, add an additional layer of
+                        * defense.
+                        */
+                       DBG_ERR("Rejecting root override, invalid stat [%s]\n",
+                               smb_fname_str_dbg(smb_fname));
+                       return NT_STATUS_ACCESS_DENIED;
+               }
+
+               become_root();
+               sizeret = SMB_VFS_GETXATTR(conn, smb_fname,
+                                          SAMBA_XATTR_DOS_ATTRIB,
+                                          attrstr,
+                                          sizeof(attrstr));
+               if (sizeret == -1) {
+                       saved_errno = errno;
+               }
+               unbecome_root();
+
+               if (saved_errno != 0) {
+                       errno = saved_errno;
+               }
+       }
        if (sizeret == -1) {
                DBG_INFO("Cannot get attribute "
                         "from EA on file %s: Error = %s\n",
@@ -291,79 +407,11 @@ NTSTATUS get_ea_dos_attribute(connection_struct *conn,
        blob.data = (uint8_t *)attrstr;
        blob.length = sizeret;
 
-       ndr_err = ndr_pull_struct_blob(&blob, talloc_tos(), &dosattrib,
-                       (ndr_pull_flags_fn_t)ndr_pull_xattr_DOSATTRIB);
-
-       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-               DEBUG(1,("get_ea_dos_attribute: bad ndr decode "
-                        "from EA on file %s: Error = %s\n",
-                        smb_fname_str_dbg(smb_fname),
-                        ndr_errstr(ndr_err)));
-               return ndr_map_error2ntstatus(ndr_err);
-       }
-
-       DEBUG(10,("get_ea_dos_attribute: %s attr = %s\n",
-                 smb_fname_str_dbg(smb_fname), dosattrib.attrib_hex));
-
-       switch (dosattrib.version) {
-               case 0xFFFF:
-                       dosattr = dosattrib.info.compatinfoFFFF.attrib;
-                       break;
-               case 1:
-                       dosattr = dosattrib.info.info1.attrib;
-                       if (!null_nttime(dosattrib.info.info1.create_time)) {
-                               struct timespec create_time =
-                                       nt_time_to_unix_timespec(
-                                               dosattrib.info.info1.create_time);
-
-                               update_stat_ex_create_time(&smb_fname->st,
-                                                       create_time);
-
-                               DEBUG(10,("get_ea_dos_attribute: file %s case 1 "
-                                       "set btime %s\n",
-                                       smb_fname_str_dbg(smb_fname),
-                                       time_to_asc(convert_timespec_to_time_t(
-                                               create_time)) ));
-                       }
-                       break;
-               case 2:
-                       dosattr = dosattrib.info.oldinfo2.attrib;
-                       /* Don't know what flags to check for this case. */
-                       break;
-               case 3:
-                       dosattr = dosattrib.info.info3.attrib;
-                       if ((dosattrib.info.info3.valid_flags & XATTR_DOSINFO_CREATE_TIME) &&
-                                       !null_nttime(dosattrib.info.info3.create_time)) {
-                               struct timespec create_time =
-                                       nt_time_to_unix_timespec(
-                                               dosattrib.info.info3.create_time);
-
-                               update_stat_ex_create_time(&smb_fname->st,
-                                                       create_time);
-
-                               DEBUG(10,("get_ea_dos_attribute: file %s case 3 "
-                                       "set btime %s\n",
-                                       smb_fname_str_dbg(smb_fname),
-                                       time_to_asc(convert_timespec_to_time_t(
-                                               create_time)) ));
-                       }
-                       break;
-               default:
-                       DEBUG(1,("get_ea_dos_attribute: Badly formed DOSATTRIB on "
-                                "file %s - %s\n", smb_fname_str_dbg(smb_fname),
-                                attrstr));
-                       /* Should this be INTERNAL_ERROR? */
-                       return NT_STATUS_INVALID_PARAMETER;
+       status = parse_dos_attribute_blob(smb_fname, blob, pattr);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
        }
 
-       if (S_ISDIR(smb_fname->st.st_ex_mode)) {
-               dosattr |= FILE_ATTRIBUTE_DIRECTORY;
-       }
-       /* FILE_ATTRIBUTE_SPARSE is valid on get but not on set. */
-       *pattr |= (uint32_t)(dosattr & (SAMBA_ATTRIBUTES_MASK|FILE_ATTRIBUTE_SPARSE));
-
-       dos_mode_debug_print(__func__, *pattr);
-
        return NT_STATUS_OK;
 }
 
@@ -379,6 +427,7 @@ NTSTATUS set_ea_dos_attribute(connection_struct *conn,
        struct xattr_DOSATTRIB dosattrib;
        enum ndr_err_code ndr_err;
        DATA_BLOB blob;
+       int ret;
 
        if (!lp_store_dos_attributes(SNUM(conn))) {
                return NT_STATUS_NOT_IMPLEMENTED;
@@ -420,14 +469,16 @@ NTSTATUS set_ea_dos_attribute(connection_struct *conn,
                return NT_STATUS_INVALID_PARAMETER;
        }
 
-       if (SMB_VFS_SETXATTR(conn, smb_fname->base_name,
-                            SAMBA_XATTR_DOS_ATTRIB, blob.data, blob.length,
-                            0) == -1) {
+       ret = SMB_VFS_SETXATTR(conn, smb_fname,
+                              SAMBA_XATTR_DOS_ATTRIB,
+                              blob.data, blob.length, 0);
+       if (ret != 0) {
                NTSTATUS status = NT_STATUS_OK;
                bool need_close = false;
                files_struct *fsp = NULL;
+               bool set_dosmode_ok = false;
 
-               if((errno != EPERM) && (errno != EACCES)) {
+               if ((errno != EPERM) && (errno != EACCES)) {
                        DBG_INFO("Cannot set "
                                 "attribute EA on file %s: Error = %s\n",
                                 smb_fname_str_dbg(smb_fname), strerror(errno));
@@ -439,10 +490,21 @@ NTSTATUS set_ea_dos_attribute(connection_struct *conn,
                */
 
                /* Check if we have write access. */
-               if(!CAN_WRITE(conn) || !lp_dos_filemode(SNUM(conn)))
+               if (!CAN_WRITE(conn)) {
                        return NT_STATUS_ACCESS_DENIED;
+               }
 
-               if (!can_write_to_file(conn, smb_fname)) {
+               status = smbd_check_access_rights(conn, smb_fname, false,
+                                                 FILE_WRITE_ATTRIBUTES);
+               if (NT_STATUS_IS_OK(status)) {
+                       set_dosmode_ok = true;
+               }
+
+               if (!set_dosmode_ok && lp_dos_filemode(SNUM(conn))) {
+                       set_dosmode_ok = can_write_to_file(conn, smb_fname);
+               }
+
+               if (!set_dosmode_ok) {
                        return NT_STATUS_ACCESS_DENIED;
                }
 
@@ -460,9 +522,10 @@ NTSTATUS set_ea_dos_attribute(connection_struct *conn,
                }
 
                become_root();
-               if (SMB_VFS_FSETXATTR(fsp,
-                                    SAMBA_XATTR_DOS_ATTRIB, blob.data,
-                                    blob.length, 0) == 0) {
+               ret = SMB_VFS_FSETXATTR(fsp,
+                                       SAMBA_XATTR_DOS_ATTRIB,
+                                       blob.data, blob.length, 0);
+               if (ret == 0) {
                        status = NT_STATUS_OK;
                }
                unbecome_root();
@@ -622,7 +685,34 @@ uint32_t dos_mode(connection_struct *conn, struct smb_filename *smb_fname)
        /* Get the DOS attributes via the VFS if we can */
        status = SMB_VFS_GET_DOS_ATTRIBUTES(conn, smb_fname, &result);
        if (!NT_STATUS_IS_OK(status)) {
-               result |= dos_mode_from_sbuf(conn, smb_fname);
+               /*
+                * Only fall back to using UNIX modes if we get NOT_IMPLEMENTED.
+                */
+               if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+                       result |= dos_mode_from_sbuf(conn, smb_fname);
+               }
+       }
+
+       /*
+        * According to MS-FSA a stream name does not have
+        * separate DOS attribute metadata, so we must return
+        * the DOS attribute from the base filename. With one caveat,
+        * a non-default stream name can never be a directory.
+        *
+        * As this is common to all streams data stores, we handle
+        * it here instead of inside all stream VFS modules.
+        *
+        * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13380
+        */
+
+       if (is_ntfs_stream_smb_fname(smb_fname)) {
+               /* is_ntfs_stream_smb_fname() returns false for a POSIX path. */
+               if (!is_ntfs_default_stream_smb_fname(smb_fname)) {
+                       /*
+                        * Non-default stream name, not a posix path.
+                        */
+                       result &= ~(FILE_ATTRIBUTE_DIRECTORY);
+               }
        }
 
        if (conn->fs_capabilities & FILE_FILE_COMPRESSION) {
@@ -1111,7 +1201,7 @@ static NTSTATUS get_file_handle_for_metadata(connection_struct *conn,
                NULL,                                   /* req */
                0,                                      /* root_dir_fid */
                smb_fname_cp,                           /* fname */
-               FILE_WRITE_DATA,                        /* access_mask */
+               FILE_WRITE_ATTRIBUTES,                  /* access_mask */
                (FILE_SHARE_READ | FILE_SHARE_WRITE |   /* share_access */
                        FILE_SHARE_DELETE),
                FILE_OPEN,                              /* create_disposition*/