diff --git a/src/ioslaves/file/file.h b/src/ioslaves/file/file.h --- a/src/ioslaves/file/file.h +++ b/src/ioslaves/file/file.h @@ -106,6 +106,13 @@ PrivilegeOperationReturnValue execWithElevatedPrivilege(ActionType action, const QVariant &arg1, const QVariant &arg2 = QVariant(), const QVariant &arg3 = QVariant()); + PrivilegeOperationReturnValue tryOpen(QFile &f, const QByteArray &path, int flags, int mode); + + // We want to execute chmod/chown/utime with elevated privileges (in copy & put) + // only during the brief period privileges are elevated. If it's not the case show + // a warning and continue. + PrivilegeOperationReturnValue tryChangeFileAttr(ActionType action, const QVariant &arg1, + const QVariant &arg2, const QVariant &arg3 = QVariant()); private: mutable QHash mUsercache; mutable QHash mGroupcache; diff --git a/src/ioslaves/file/file_unix.cpp b/src/ioslaves/file/file_unix.cpp --- a/src/ioslaves/file/file_unix.cpp +++ b/src/ioslaves/file/file_unix.cpp @@ -41,6 +41,8 @@ #include +#include "fdreceiver.h" + //sendfile has different semantics in different platforms #if defined HAVE_SENDFILE && defined Q_OS_LINUX #define USE_SENDFILE 1 @@ -76,9 +78,60 @@ && (requestPrivilegeOperation() == KIO::OperationAllowed); } +PrivilegeOperationReturnValue FileProtocol::tryOpen(QFile &f, const QByteArray &path, int flags, int mode) +{ + FdReceiver fdRecv(socketPath()); + if (!fdRecv.isListening()) { + return PrivilegeOperationReturnValue::failure(); + } + + QIODevice::OpenMode openMode; + if (flags & O_RDONLY) { + openMode |= QIODevice::ReadOnly; + } + if (flags & O_WRONLY || flags & O_CREAT) { + openMode |= QIODevice::WriteOnly; + } + if (flags & O_RDWR) { + openMode |= QIODevice::ReadWrite; + } + if (flags & O_TRUNC) { + openMode |= QIODevice::Truncate; + } + if (flags & O_APPEND) { + openMode |= QIODevice::Append; + } + + if (auto err = execWithElevatedPrivilege(OPEN, path, flags, mode)) { + return err; + } else { + int fd = fdRecv.fileDescriptor(); + if (fd < 3 || !f.open(fd, openMode, QFileDevice::AutoCloseHandle)) { + return PrivilegeOperationReturnValue::failure(); + } + } + return PrivilegeOperationReturnValue::success(); +} + +PrivilegeOperationReturnValue FileProtocol::tryChangeFileAttr(ActionType action, const QVariant &arg1, + const QVariant &arg2, const QVariant &arg3) +{ + KAuth::Action execAction(QStringLiteral("org.kde.kio.file.exec")); + execAction.setHelperId(QStringLiteral("org.kde.kio.file")); + if (execAction.status() == KAuth::Action::AuthorizedStatus) { + return execWithElevatedPrivilege(action, arg1, arg2, arg3); + } + return PrivilegeOperationReturnValue::failure(); +} + void FileProtocol::copy(const QUrl &srcUrl, const QUrl &destUrl, int _mode, JobFlags _flags) { + if (privilegeOperationUnitTestMode()) { + finished(); + return; + } + // qDebug() << "copy(): " << srcUrl << " -> " << destUrl << ", mode=" << _mode; const QString src = srcUrl.toLocalFile(); @@ -131,34 +184,60 @@ // the symlink actually points to current source! if ((_flags & KIO::Overwrite) && ((buff_dest.st_mode & QT_STAT_MASK) == QT_STAT_LNK)) { //qDebug() << "copy(): LINK DESTINATION"; - QFile::remove(dest); + if (!QFile::remove(dest)) { + if (auto err = execWithElevatedPrivilege(DEL, _dest)) { + if (!err.wasCanceled()) { + error(KIO::ERR_CANNOT_DELETE_ORIGINAL, dest); + } + return; + } + } } } QFile src_file(src); if (!src_file.open(QIODevice::ReadOnly)) { - error(KIO::ERR_CANNOT_OPEN_FOR_READING, src); - return; + if (auto err = tryOpen(src_file, _src, O_RDONLY, S_IRUSR)) { + if (!err.wasCanceled()) { + error(KIO::ERR_CANNOT_OPEN_FOR_READING, src); + } + return; + } } #if HAVE_FADVISE posix_fadvise(src_file.handle(), 0, 0, POSIX_FADV_SEQUENTIAL); #endif QFile dest_file(dest); if (!dest_file.open(QIODevice::Truncate | QIODevice::WriteOnly)) { - // qDebug() << "###### COULD NOT WRITE " << dest; - if (errno == EACCES) { - error(KIO::ERR_WRITE_ACCESS_DENIED, dest); - } else { - error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest); + if (auto err = tryOpen(dest_file, _dest, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR)) { + if (!err.wasCanceled()) { + // qDebug() << "###### COULD NOT WRITE " << dest; + if (errno == EACCES) { + error(KIO::ERR_WRITE_ACCESS_DENIED, dest); + } else { + error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest); + } + } + src_file.close(); + return; } - src_file.close(); - return; } // nobody shall be allowed to peek into the file during creation - dest_file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner); + if (!QFile::setPermissions(dest, QFileDevice::ReadOwner | QFileDevice::WriteOwner)) { + if (auto err = execWithElevatedPrivilege(CHOWN, _dest, getuid(), getgid())) { + dest_file.close(); + execWithElevatedPrivilege(DEL, _dest); + if (!err.wasCanceled()) { + error(KIO::ERR_CANNOT_CHOWN, dest); + } + return; + } else { + QFile::setPermissions(dest, QFileDevice::ReadOwner | QFileDevice::WriteOwner); + } + } #if HAVE_FADVISE posix_fadvise(dest_file.handle(), 0, 0, POSIX_FADV_SEQUENTIAL); @@ -219,7 +298,9 @@ acl_free(acl); } #endif - dest_file.remove(); // don't keep partly copied file + if (!QFile::remove(dest)) { // don't keep partly copied file + execWithElevatedPrivilege(DEL, _dest); + } return; } if (n == 0) { @@ -240,7 +321,9 @@ acl_free(acl); } #endif - dest_file.remove(); // don't keep partly copied file + if (!QFile::remove(dest)) { // don't keep partly copied file + execWithElevatedPrivilege(DEL, _dest); + } return; } processed_size += n; @@ -261,7 +344,9 @@ acl_free(acl); } #endif - dest_file.remove(); // don't keep partly copied file + if (!QFile::remove(dest)) { // don't keep partly copied file + execWithElevatedPrivilege(DEL, _dest); + } return; } @@ -276,9 +361,7 @@ || (acl && acl_set_file(_dest.data(), ACL_TYPE_ACCESS, acl) != 0) #endif ) { - KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(dest); - // Eat the error if the filesystem apparently doesn't support chmod. - if (mp && mp->testFileSystemFlag(KMountPoint::SupportsChmod)) { + if (tryChangeFileAttr(CHMOD, _dest, _mode)) { warning(i18n("Could not change permissions for '%1'", dest)); } } @@ -294,15 +377,19 @@ // we might not be allowed to change the owner (void)::chown(_dest.data(), buff_src.st_uid, -1 /*keep group*/); } else { - qCWarning(KIO_FILE) << QStringLiteral("Couldn't preserve group for '%1'").arg(dest); + if (tryChangeFileAttr(CHOWN, _dest, buff_src.st_uid, buff_src.st_gid)) { + qCWarning(KIO_FILE) << QStringLiteral("Couldn't preserve group for '%1'").arg(dest); + } } // copy access and modification time struct utimbuf ut; ut.actime = buff_src.st_atime; ut.modtime = buff_src.st_mtime; if (::utime(_dest.data(), &ut) != 0) { - qCWarning(KIO_FILE) << QStringLiteral("Couldn't preserve access and modification time for '%1'").arg(dest); + if (tryChangeFileAttr(UTIME, _dest, qint64(ut.actime), qint64(ut.modtime))) { + qCWarning(KIO_FILE) << QStringLiteral("Couldn't preserve access and modification time for '%1'").arg(dest); + } } processedSize(buff_src.st_size); diff --git a/src/ioslaves/file/file_win.cpp b/src/ioslaves/file/file_win.cpp --- a/src/ioslaves/file/file_win.cpp +++ b/src/ioslaves/file/file_win.cpp @@ -392,3 +392,12 @@ { return PrivilegeOperationReturnValue::failure(); } +PrivilegeOperationReturnValue tryOpen(QFile &f, const QByteArray &, int , int) +{ + return PrivilegeOperationReturnValue::failure(); +} + +PrivilegeOperationReturnValue tryChangeFileAttr(ActionType , const QVariant &, const QVariant &, const QVariant &) +{ + return PrivilegeOperationReturnValue::failure(); +}