Index: src/core/global.h =================================================================== --- src/core/global.h +++ src/core/global.h @@ -262,6 +262,16 @@ }; /** + * Specifies privilege file operation status. + * @since 5.38 + */ +enum PrivilegeOperationStatus { + OperationAllowed = 1, + OperationCanceled, + OperationNotAllowed +}; + +/** * Parses the string representation of the cache control option. * * @param cacheControl the string representation Index: src/core/slavebase.h =================================================================== --- src/core/slavebase.h +++ src/core/slavebase.h @@ -935,6 +935,14 @@ */ int waitForHostInfo(QHostInfo &info); + /** + * Checks with job if privilege operation is allowed. + * @return privilege operation status. + * @see PrivilegeOperationStatus + * @since 5.38 + */ + PrivilegeOperationStatus requestPrivilegeOperation(); + protected: /** * Name of the protocol supported by this slave Index: src/core/slavebase.cpp =================================================================== --- src/core/slavebase.cpp +++ src/core/slavebase.cpp @@ -1457,3 +1457,11 @@ return result; } + +PrivilegeOperationStatus SlaveBase::requestPrivilegeOperation() +{ + QByteArray buffer; + send(MSG_PRIVILEGE_EXEC); + waitForAnswer(MSG_PRIVILEGE_EXEC, 0, buffer); + return KIO::PrivilegeOperationStatus(buffer.toInt()); +} Index: src/core/slaveinterface.h =================================================================== --- src/core/slaveinterface.h +++ src/core/slaveinterface.h @@ -84,7 +84,8 @@ MSG_DEL_AUTH_KEY, ///< @deprecated MSG_OPENED, MSG_WRITTEN, - MSG_HOST_INFO_REQ + MSG_HOST_INFO_REQ, + MSG_PRIVILEGE_EXEC // add new ones here once a release is done, to avoid breaking binary compatibility }; @@ -144,6 +145,8 @@ void open(); void written(KIO::filesize_t); + void privilegeOperationRequested(); + /////////// // Info sent by the slave ////////// Index: src/core/slaveinterface.cpp =================================================================== --- src/core/slaveinterface.cpp +++ src/core/slaveinterface.cpp @@ -319,6 +319,9 @@ HostInfo::lookupHost(hostName, this, SLOT(slotHostInfo(QHostInfo))); break; } + case MSG_PRIVILEGE_EXEC: + emit privilegeOperationRequested(); + break; default: qCWarning(KIO_CORE) << "Slave sends unknown command (" << _cmd << "), dropping slave"; return false; Index: src/ioslaves/file/file.h =================================================================== --- src/ioslaves/file/file.h +++ src/ioslaves/file/file.h @@ -39,6 +39,8 @@ #include #endif +#include "file_p.h" + #include Q_DECLARE_LOGGING_CATEGORY(KIO_FILE) @@ -100,6 +102,9 @@ void fileSystemFreeSpace(const QUrl &url); // KF6 TODO: Turn into virtual method in SlaveBase + PrivilegeOperationReturnValue execWithElevatedPrivilege(ActionType action, const QVariant &arg1, + const QVariant &arg2 = QVariant(), + const QVariant &arg3 = QVariant()); private: mutable QHash mUsercache; mutable QHash mGroupcache; Index: src/ioslaves/file/file_p.h =================================================================== --- src/ioslaves/file/file_p.h +++ src/ioslaves/file/file_p.h @@ -34,4 +34,23 @@ UTIME, }; +class PrivilegeOperationReturnValue +{ +public: + static PrivilegeOperationReturnValue success() { return PrivilegeOperationReturnValue{true, false, false}; } + static PrivilegeOperationReturnValue failure() { return PrivilegeOperationReturnValue{false, true, false}; } + static PrivilegeOperationReturnValue canceled() { return PrivilegeOperationReturnValue{false, false, true}; } + operator bool() const { return m_success; } + bool failed() const { return m_failure; } + bool wasCanceled() const { return m_canceled; } +private: + PrivilegeOperationReturnValue(bool success, bool failure, bool canceled) : m_success(success), m_failure(failure), m_canceled(canceled) {} + const bool m_success; + const bool m_failure; + const bool m_canceled; +}; + +class QString; +const QString socketPath(); + #endif Index: src/ioslaves/file/file_unix.cpp =================================================================== --- src/ioslaves/file/file_unix.cpp +++ src/ioslaves/file/file_unix.cpp @@ -39,6 +39,8 @@ #include #include +#include + //sendfile has different semantics in different platforms #if defined HAVE_SENDFILE && defined Q_OS_LINUX #define USE_SENDFILE 1 @@ -63,6 +65,11 @@ return false; } +const QString socketPath() +{ + return QStringLiteral("org_kde_kio_file_helper_%1").arg(getpid()); +} + void FileProtocol::copy(const QUrl &srcUrl, const QUrl &destUrl, int _mode, JobFlags _flags) { @@ -650,3 +657,60 @@ finished(); } + +PrivilegeOperationReturnValue FileProtocol::execWithElevatedPrivilege(ActionType action, const QVariant &arg1, + const QVariant &arg2, const QVariant &arg3) +{ + if (!(errno == EACCES || errno == EPERM)) { + return PrivilegeOperationReturnValue::failure(); + } + + KIO::PrivilegeOperationStatus opStatus = requestPrivilegeOperation(); + if (opStatus != KIO::OperationAllowed) { + if (opStatus == KIO::OperationCanceled) { + error(KIO::ERR_USER_CANCELED, QString()); + return PrivilegeOperationReturnValue::canceled(); + } + return PrivilegeOperationReturnValue::failure(); + } + + KAuth::Action execAction(QStringLiteral("org.kde.kio.file.exec")); + execAction.setHelperId(QStringLiteral("org.kde.kio.file")); + + // if we are unit testing let's pretend to execute the action. + if (metaData(QStringLiteral("UnitTesting")) == QLatin1String("true")) { + const QString metaData = execAction.name() + ',' + + (execAction.isValid() ? "true" : "false") + ',' + + execAction.helperId() + ',' + + (execAction.status() == KAuth::Action::AuthRequiredStatus ? "true" : "false"); + setMetaData(QStringLiteral("TestData"), metaData); + return PrivilegeOperationReturnValue::success(); + } + + if (action == CHMOD || action == CHOWN || action == UTIME) { + KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(arg1.toString()); + // Test for chmod and utime will return the same result as test for chown. + if (mp && !mp->testFileSystemFlag(KMountPoint::SupportsChown)) { + return PrivilegeOperationReturnValue::failure(); + } + } + + QByteArray helperArgs; + QDataStream out(&helperArgs, QIODevice::WriteOnly); + out << action << arg1 << arg2 << arg3; + + if (action == OPEN || action == OPENDIR) { + out << socketPath(); + } + + QVariantMap argv; + argv.insert(QStringLiteral("arguments"), helperArgs); + execAction.setArguments(argv); + + auto reply = execAction.execute(); + if (reply->exec()) { + return PrivilegeOperationReturnValue::success(); + } + + return PrivilegeOperationReturnValue::failure(); +} Index: src/ioslaves/file/file_win.cpp =================================================================== --- src/ioslaves/file/file_win.cpp +++ src/ioslaves/file/file_win.cpp @@ -374,3 +374,9 @@ finished(); } + +PrivilegeOperationReturnValue FileProtocol::execWithElevatedPrivilege(int, ActionType, const QVariant &, + const QVariant &, const QVariant &) +{ + return PrivilegeOperationReturnValue::failure(); +}