Skip to content

Commit

Permalink
newly created folders will be read-only when needed
Browse files Browse the repository at this point in the history
Close #6296

Signed-off-by: Matthieu Gallien <[email protected]>
  • Loading branch information
mgallien committed Mar 11, 2024
1 parent b953aa6 commit 10036e1
Show file tree
Hide file tree
Showing 9 changed files with 540 additions and 16 deletions.
9 changes: 7 additions & 2 deletions src/common/filesystembase.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@

#include "config.h"

#include "csync/ocsynclib.h"

#include <QString>
#include <ctime>
#include <QFileInfo>
#include <QLoggingCategory>

#include <csync/ocsynclib.h>
#include <ctime>

class QFile;

Expand All @@ -42,6 +43,10 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcFileSystem)
* @brief This file contains file system helper
*/
namespace FileSystem {
enum class FolderPermissions {
ReadOnly,
ReadWrite,
};

/**
* @brief Mark the file as hidden (only has effects on windows)
Expand Down
179 changes: 176 additions & 3 deletions src/libsync/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@
#include "filesystem.h"

#include "common/utility.h"
#include "csync.h"
#include "vio/csync_vio_local.h"
#include "std/c_time.h"

#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QDirIterator>
#include <QCoreApplication>

#include "csync.h"
#include "vio/csync_vio_local.h"
#include "std/c_time.h"
#ifdef Q_OS_WIN
#include <securitybaseapi.h>
#include <sddl.h>
#endif

namespace OCC {

Expand Down Expand Up @@ -189,5 +194,173 @@ bool FileSystem::getInode(const QString &filename, quint64 *inode)
return false;
}

bool FileSystem::setFolderPermissions(const QString &path,
FileSystem::FolderPermissions permissions) noexcept
{
try {
switch (permissions) {
case OCC::FileSystem::FolderPermissions::ReadOnly:
std::filesystem::permissions(path.toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove);
break;
case OCC::FileSystem::FolderPermissions::ReadWrite:
break;
}
}
catch (const std::filesystem::filesystem_error &e)
{
qCWarning(lcFileSystem()) << "exception when modifying folder permissions" << e.what() << e.path1().c_str() << e.path2().c_str();
return false;
}

#ifdef Q_OS_WIN
SECURITY_INFORMATION info = DACL_SECURITY_INFORMATION;
std::unique_ptr<char[]> securityDescriptor;
auto neededLength = 0ul;

if (!GetFileSecurityW(path.toStdWString().c_str(), info, nullptr, 0, &neededLength)) {
const auto lastError = GetLastError();
if (lastError != ERROR_INSUFFICIENT_BUFFER) {
qCWarning(lcFileSystem) << "error when calling GetFileSecurityW" << path << lastError;
return false;
}

securityDescriptor.reset(new char[neededLength]);

if (!GetFileSecurityW(path.toStdWString().c_str(), info, securityDescriptor.get(), neededLength, &neededLength)) {
qCWarning(lcFileSystem) << "error when calling GetFileSecurityW" << path << GetLastError();
return false;
}
}

int daclPresent = false, daclDefault = false;
PACL resultDacl = nullptr;
if (!GetSecurityDescriptorDacl(securityDescriptor.get(), &daclPresent, &resultDacl, &daclDefault)) {
qCWarning(lcFileSystem) << "error when calling GetSecurityDescriptorDacl" << path << GetLastError();
return false;
}
if (!daclPresent) {
qCWarning(lcFileSystem) << "error when calling DACL needed to set a folder read-only or read-write is missing" << path;
return false;
}

PSID sid = nullptr;
if (!ConvertStringSidToSidA("S-1-5-32-545", &sid))
{
qCWarning(lcFileSystem) << "error when calling ConvertStringSidToSidA" << path << GetLastError();
return false;
}

ACL_SIZE_INFORMATION aclSize;
if (!GetAclInformation(resultDacl, &aclSize, sizeof(aclSize), AclSizeInformation)) {
qCWarning(lcFileSystem) << "error when calling GetAclInformation" << path << GetLastError();
return false;
}

const auto newAclSize = aclSize.AclBytesInUse + sizeof(ACCESS_DENIED_ACE) + GetLengthSid(sid);
qCDebug(lcFileSystem) << "allocated a new DACL object of size" << newAclSize;

std::unique_ptr<ACL> newDacl{reinterpret_cast<PACL>(new char[newAclSize])};
if (!InitializeAcl(newDacl.get(), newAclSize, ACL_REVISION)) {
const auto lastError = GetLastError();
if (lastError != ERROR_INSUFFICIENT_BUFFER) {
qCWarning(lcFileSystem) << "insufficient memory error when calling InitializeAcl" << path;
return false;
}

qCWarning(lcFileSystem) << "error when calling InitializeAcl" << path << lastError;
return false;
}

if (permissions == FileSystem::FolderPermissions::ReadOnly) {
qCInfo(lcFileSystem) << path << "will be read only";
if (!AddAccessDeniedAce(newDacl.get(), ACL_REVISION, FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | FILE_DELETE_CHILD, sid)) {
qCWarning(lcFileSystem) << "error when calling AddAccessDeniedAce << path" << GetLastError();
return false;
}
}

for (int i = 0; i < aclSize.AceCount; ++i) {
void *currentAce = nullptr;
if (!GetAce(resultDacl, i, &currentAce)) {
qCWarning(lcFileSystem) << "error when calling GetAce" << path << GetLastError();
return false;
}

const auto currentAceHeader = reinterpret_cast<PACE_HEADER>(currentAce);

if (permissions == FileSystem::FolderPermissions::ReadWrite) {
qCInfo(lcFileSystem) << path << "will be read write";
}
if (permissions == FileSystem::FolderPermissions::ReadWrite && (ACCESS_DENIED_ACE_TYPE == (currentAceHeader->AceType & ACCESS_DENIED_ACE_TYPE))) {
qCWarning(lcFileSystem) << "AceHeader" << path << currentAceHeader->AceFlags << currentAceHeader->AceSize << currentAceHeader->AceType;
continue;
}

if (!AddAce(newDacl.get(), ACL_REVISION, i + 1, currentAce, currentAceHeader->AceSize)) {
const auto lastError = GetLastError();
if (lastError != ERROR_INSUFFICIENT_BUFFER) {
qCWarning(lcFileSystem) << "insufficient memory error when calling AddAce" << path;
return false;
}

if (lastError != ERROR_INVALID_PARAMETER) {
qCWarning(lcFileSystem) << "invalid parameter error when calling AddAce" << path << "ACL size" << newAclSize;
return false;
}

qCWarning(lcFileSystem) << "error when calling AddAce" << path << lastError << "acl index" << (i + 1);
return false;
}
}

SECURITY_DESCRIPTOR newSecurityDescriptor;
if (!InitializeSecurityDescriptor(&newSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION)) {
qCWarning(lcFileSystem) << "error when calling InitializeSecurityDescriptor" << path << GetLastError();
return false;
}

if (!SetSecurityDescriptorDacl(&newSecurityDescriptor, true, newDacl.get(), false)) {
qCWarning(lcFileSystem) << "error when calling SetSecurityDescriptorDacl" << path << GetLastError();
return false;
}

if (!SetFileSecurityW(path.toStdWString().c_str(), info, &newSecurityDescriptor)) {
qCWarning(lcFileSystem) << "error when calling SetFileSecurityW" << path << GetLastError();
return false;
}
#endif

try {
switch (permissions) {
case OCC::FileSystem::FolderPermissions::ReadOnly:
break;
case OCC::FileSystem::FolderPermissions::ReadWrite:
std::filesystem::permissions(path.toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add);
break;
}
}
catch (const std::filesystem::filesystem_error &e)
{
qCWarning(lcFileSystem()) << "exception when modifying folder permissions" << e.what() << e.path1().c_str() << e.path2().c_str();
return false;
}

return true;
}

bool FileSystem::isFolderReadOnly(const std::filesystem::path &path) noexcept
{
try {
const auto folderStatus = std::filesystem::status(path);
const auto folderPermissions = folderStatus.permissions();
return (folderPermissions & std::filesystem::perms::owner_write) != std::filesystem::perms::owner_write;
}
catch (const std::filesystem::filesystem_error &e)
{
qCWarning(lcFileSystem()) << "exception when checking folder permissions" << e.what() << e.path1().c_str() << e.path2().c_str();
return false;
}
}


} // namespace OCC
14 changes: 10 additions & 4 deletions src/libsync/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@

#include "config.h"

#include "owncloudlib.h"
#include "common/filesystembase.h"

#include <QString>

#include <ctime>
#include <functional>

#include <owncloudlib.h>
// Chain in the base include and extend the namespace
#include "common/filesystembase.h"
#include <filesystem>

class QFile;

Expand Down Expand Up @@ -96,6 +97,11 @@ namespace FileSystem {
bool OWNCLOUDSYNC_EXPORT removeRecursively(const QString &path,
const std::function<void(const QString &path, bool isDir)> &onDeleted = nullptr,
QStringList *errors = nullptr);

bool OWNCLOUDSYNC_EXPORT setFolderPermissions(const QString &path,
FileSystem::FolderPermissions permissions) noexcept;

bool OWNCLOUDSYNC_EXPORT isFolderReadOnly(const std::filesystem::path &path) noexcept;
}

/** @} */
Expand Down
55 changes: 54 additions & 1 deletion src/libsync/owncloudpropagator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,59 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status)
if (_item->_instruction == CSYNC_INSTRUCTION_RENAME
|| _item->_instruction == CSYNC_INSTRUCTION_NEW
|| _item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA) {

if (!_item->_remotePerm.isNull() &&
!_item->_remotePerm.hasPermission(RemotePermissions::CanAddFile) &&
!_item->_remotePerm.hasPermission(RemotePermissions::CanRename) &&
!_item->_remotePerm.hasPermission(RemotePermissions::CanMove) &&
!_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories)) {
try {
if (QFileInfo::exists(propagator()->fullLocalPath(_item->_file))) {
FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_file), FileSystem::FolderPermissions::ReadOnly);
qCDebug(lcDirectory) << "old permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions());
std::filesystem::permissions(propagator()->fullLocalPath(_item->_file).toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove);
qCDebug(lcDirectory) << "new permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions());
}
if (!_item->_renameTarget.isEmpty() && QFileInfo::exists(propagator()->fullLocalPath(_item->_renameTarget))) {
FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_renameTarget), FileSystem::FolderPermissions::ReadOnly);
qCDebug(lcDirectory) << "old permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions());
std::filesystem::permissions(propagator()->fullLocalPath(_item->_renameTarget).toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove);
qCDebug(lcDirectory) << "new permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions());
}
}
catch (const std::filesystem::filesystem_error &e)
{
qCWarning(lcDirectory) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
_item->_status = SyncFileItem::NormalError;
_item->_errorString = tr("The folder %1 cannot be made read-only: %2").arg(_item->_file, e.what());
}
} else if (!_item->_remotePerm.isNull() &&
(_item->_remotePerm.hasPermission(RemotePermissions::CanAddFile) ||
!_item->_remotePerm.hasPermission(RemotePermissions::CanRename) ||
!_item->_remotePerm.hasPermission(RemotePermissions::CanMove) ||
!_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories))) {
try {
if (QFileInfo::exists(propagator()->fullLocalPath(_item->_file))) {
FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_file), FileSystem::FolderPermissions::ReadWrite);
qCDebug(lcDirectory) << "old permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions());
std::filesystem::permissions(propagator()->fullLocalPath(_item->_file).toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add);
qCDebug(lcDirectory) << "new permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions());
}
if (!_item->_renameTarget.isEmpty() && QFileInfo::exists(propagator()->fullLocalPath(_item->_renameTarget))) {
FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_renameTarget), FileSystem::FolderPermissions::ReadWrite);
qCDebug(lcDirectory) << "old permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions());
std::filesystem::permissions(propagator()->fullLocalPath(_item->_renameTarget).toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add);
qCDebug(lcDirectory) << "new permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions());
}
}
catch (const std::filesystem::filesystem_error &e)
{
qCWarning(lcDirectory) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
_item->_status = SyncFileItem::NormalError;
_item->_errorString = tr("The folder %1 cannot be made read-only: %2").arg(e.path1().c_str(), e.what());
}
}

const auto result = propagator()->updateMetadata(*_item);
if (!result) {
status = _item->_status = SyncFileItem::FatalError;
Expand All @@ -1454,7 +1507,7 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status)
}
}
_state = Finished;
qCInfo(lcPropagator) << "PropagateDirectory::slotSubJobsFinished" << "emit finished" << status;
qCInfo(lcPropagator) << "PropagateDirectory::slotSubJobsFinished" << "emit finished" << status << _item->_file;
emit finished(status);
}

Expand Down
31 changes: 26 additions & 5 deletions src/libsync/propagatedownload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,8 @@
#include <QNetworkAccessManager>
#include <QFileInfo>
#include <QDir>
#include <cmath>

#ifdef Q_OS_UNIX
#include <unistd.h>
#endif
#include <cmath>

namespace OCC {

Expand Down Expand Up @@ -672,8 +669,26 @@ void PropagateDownloadFile::startDownload()

// Can't open(Append) read-only files, make sure to make
// file writable if it exists.
if (_tmpFile.exists())
if (_tmpFile.exists()) {
FileSystem::setFileReadOnly(_tmpFile.fileName(), false);
}

try {
const auto newDirPath = std::filesystem::path{_tmpFile.fileName().toStdWString()};
Q_ASSERT(newDirPath.has_parent_path());
_parentPath = newDirPath.parent_path();
}
catch (const std::filesystem::filesystem_error &e)
{
qCWarning(lcPropagateDownload) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
}

if (FileSystem::isFolderReadOnly(_parentPath)) {
FileSystem::setFolderPermissions(QString::fromStdWString(_parentPath.wstring()), FileSystem::FolderPermissions::ReadWrite);
emit propagator()->touchedFile(QString::fromStdWString(_parentPath.wstring()));
_needParentFolderRestorePermissions = true;
}

if (!_tmpFile.open(QIODevice::Append | QIODevice::Unbuffered)) {
qCWarning(lcPropagateDownload) << "could not open temporary file" << _tmpFile.fileName();
done(SyncFileItem::NormalError, _tmpFile.errorString(), ErrorCategory::GenericError);
Expand Down Expand Up @@ -1272,6 +1287,12 @@ void PropagateDownloadFile::downloadFinished()
return;
}

if (_needParentFolderRestorePermissions) {
FileSystem::setFolderPermissions(QString::fromStdWString(_parentPath.wstring()), FileSystem::FolderPermissions::ReadWrite);
emit propagator()->touchedFile(QString::fromStdWString(_parentPath.wstring()));
_needParentFolderRestorePermissions = false;
}

FileSystem::setFileHidden(filename, false);

// Maybe we downloaded a newer version of the file than we thought we would...
Expand Down
5 changes: 5 additions & 0 deletions src/libsync/propagatedownload.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <QBuffer>
#include <QFile>

#include <filesystem>

namespace OCC {
class PropagateDownloadEncrypted;

Expand Down Expand Up @@ -260,5 +262,8 @@ private slots:
QElapsedTimer _stopwatch;

PropagateDownloadEncrypted *_downloadEncryptedHelper = nullptr;

std::filesystem::path _parentPath;
bool _needParentFolderRestorePermissions = false;
};
}
Loading

0 comments on commit 10036e1

Please sign in to comment.