Index: src/core/slavebase.h =================================================================== --- src/core/slavebase.h +++ src/core/slavebase.h @@ -935,6 +935,11 @@ */ int waitForHostInfo(QHostInfo &info); + /** + * Checks with job if privilege operation is allowed. + */ + bool isPrivilegeOperationAllowed(); + 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,12 @@ return result; } + +bool SlaveBase::isPrivilegeOperationAllowed() +{ + QByteArray buffer; + buffer.setNum(0); + send(MSG_PRIVILEGE_EXEC); + waitForAnswer(MSG_PRIVILEGE_EXEC, 0, buffer); + return 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/CMakeLists.txt =================================================================== --- src/ioslaves/file/CMakeLists.txt +++ src/ioslaves/file/CMakeLists.txt @@ -8,7 +8,7 @@ if(WIN32) set(kio_file_PART_SRCS file.cpp file_win.cpp ) else() - set(kio_file_PART_SRCS file.cpp file_unix.cpp ) + set(kio_file_PART_SRCS file.cpp file_unix.cpp sharefd.cpp ) endif() find_package(ACL) @@ -23,6 +23,10 @@ add_library(kio_file MODULE ${kio_file_PART_SRCS}) target_link_libraries(kio_file KF5::KIOCore KF5::I18n) +if(UNIX) + target_link_libraries(kio_file Qt5::Network KF5::Auth) +endif() + if (HAVE_VOLMGT AND CMAKE_SYSTEM_NAME MATCHES SunOS) target_link_libraries(kio_file -lvolmgt) endif () @@ -33,3 +37,7 @@ set_target_properties(kio_file PROPERTIES OUTPUT_NAME "file") install(TARGETS kio_file DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio) + +if (UNIX) + add_subdirectory(kauth) +endif() Index: src/ioslaves/file/file.h =================================================================== --- src/ioslaves/file/file.h +++ src/ioslaves/file/file.h @@ -100,6 +100,21 @@ void fileSystemFreeSpace(const QUrl &url); // KF6 TODO: Turn into virtual method in SlaveBase + enum ActionType { + CHMOD = 1, + CHOWN, + DEL, + MKDIR, + OPEN, + OPENDIR, + RENAME, + RMDIR, + SYMLINK, + UTIME, + }; + virtual bool execWithElevatedPrivilege(int error, 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_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 @@ -650,3 +652,39 @@ finished(); } + +bool FileProtocol::execWithElevatedPrivilege(int error, ActionType action, const QVariant &arg1, + const QVariant &arg2, const QVariant &arg3) +{ + if (error != EACCES || !isPrivilegeOperationAllowed()) { + return false; + } + + QByteArray helperArgs; + QDataStream out(&helperArgs, QIODevice::WriteOnly); + out << action << arg1 << arg2 << arg3; + + QVariantMap argv; + argv.insert(QStringLiteral("arguments"), helperArgs); + + KAuth::Action execAction(QStringLiteral("org.kde.kio.file.exec")); + execAction.setHelperId(QStringLiteral("org.kde.kio.file")); + execAction.setArguments(argv); + + // if we are unit testing let's pretend to execute the action. + if (metaData(QStringLiteral("UnitTesting")) == QLatin1String("true")) { + const QString metaData = execAction.name() + "," + + QString::number(execAction.isValid()) + "," + + execAction.helperId() + "," + + QString::number(execAction.status()) + ","; + setMetaData(QStringLiteral("TestData"), metaData); + return true; + } + + auto reply = execAction.execute(); + if (reply->exec()) { + return true; + } + + return false; +} Index: src/ioslaves/file/file_win.cpp =================================================================== --- src/ioslaves/file/file_win.cpp +++ src/ioslaves/file/file_win.cpp @@ -374,3 +374,9 @@ finished(); } + +bool FileProtocol::execWithElevatedPrivilege(int, ActionType, const QVariant &, + const QVariant &, const QVariant &) +{ + return false; +} Index: src/ioslaves/file/kauth/CMakeLists.txt =================================================================== --- /dev/null +++ src/ioslaves/file/kauth/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(file_helper filehelper.cpp ../sharefd.cpp) +target_link_libraries(file_helper Qt5::Network KF5::Auth KF5::I18n KF5::KIOCore) + +install(TARGETS file_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) +kauth_install_helper_files(file_helper org.kde.kio.file root) +kauth_install_actions(org.kde.kio.file file.actions) Index: src/ioslaves/file/kauth/file.actions =================================================================== --- /dev/null +++ src/ioslaves/file/kauth/file.actions @@ -0,0 +1,5 @@ +[org.kde.kio.file.exec] +Name=Execute action as root. +Description=Root privileges are required to complete the action. +Policy=auth_admin +Persistence=session Index: src/ioslaves/file/kauth/filehelper.h =================================================================== --- /dev/null +++ src/ioslaves/file/kauth/filehelper.h @@ -0,0 +1,47 @@ +/*** + Copyright (C) 2017 by Chinmoy Ranjan Pradhan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +***/ + + +#ifndef HELPER_H +#define HELPER_H + +#include + +using namespace KAuth; + +/** + * This KAuth helper is responsible for performing file operations with + * root privileges. + * + * @since 5.37 + */ +class FileHelper : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + /** + * Execute action with root privileges. + * + **/ + ActionReply exec(const QVariantMap &args); +}; + +#endif Index: src/ioslaves/file/kauth/filehelper.cpp =================================================================== --- /dev/null +++ src/ioslaves/file/kauth/filehelper.cpp @@ -0,0 +1,112 @@ +/*** + Copyright (C) 2017 by Chinmoy Ranjan Pradhan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "filehelper.h" +#include "../sharefd.h" + +static void sendFileDescriptor(int fd) +{ + FdSender fdSender; + fdSender.connectToPath("org_kde_kio_file_helper_socket"); + if (fdSender.isConnected()) { + fdSender.sendFileDescriptor(fd); + return; + } + errno = -1; +} + +enum { + CHMOD = 1, + CHOWN, + DEL, + MKDIR, + OPEN, + OPENDIR, + RENAME, + RMDIR, + SYMLINK, + UTIME, +}; + +ActionReply FileHelper::exec(const QVariantMap &args) +{ + errno = 0; + + ActionReply reply; + QByteArray data = args["arguments"].toByteArray(); + QDataStream in(data); + int action; + QVariant arg1, arg2, arg3; + in >> action >> arg1 >> arg2 >> arg3; + + // the path of an existing or a new file/dir upon which the method will operate + const QByteArray path = arg1.toByteArray(); + + if (action == CHMOD) { + int mode = arg2.toInt(); + chmod(path.data(), mode); + } else if (action == CHOWN) { + int uid = arg2.toInt(); + int gid = arg3.toInt(); + chown(path.data(), uid, gid); + } else if (action == DEL) { + unlink(path.data()); + } else if (action == MKDIR) { + mkdir(path.data(), 0777); + } else if (action == OPEN) { + int oflags = arg2.toInt(); + int mode = arg3.toInt(); + int fd = open(path.data(), oflags, mode); + sendFileDescriptor(fd); + close(fd); + } else if(action == OPENDIR) { + DIR *dp = opendir(path.data()); + int fd = dirfd(dp); + sendFileDescriptor(fd); + closedir(dp); + } else if (action == RENAME) { + const QByteArray newName = arg2.toByteArray(); + rename(path.data(), newName.data()); + } else if (action == RMDIR) { + rmdir(path.data()); + } else if (action == SYMLINK) { + const QByteArray target = arg2.toByteArray(); + symlink(target.data(), path.data()); + } else if (action == UTIME) { + utimbuf ut; + ut.actime = arg2.toULongLong(); + ut.modtime = arg3.toULongLong(); + utime(path.data(), &ut); + } + + if (errno) + reply.setError(errno); + + return reply; +} + + +KAUTH_HELPER_MAIN("org.kde.kio.file", FileHelper) Index: src/ioslaves/file/sharefd.h =================================================================== --- /dev/null +++ src/ioslaves/file/sharefd.h @@ -0,0 +1,57 @@ +/*** + Copyright (C) 2017 by Chinmoy Ranjan Pradhan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +***/ + +#ifndef SHAREFD_H +#define SHAREFD_H + +#include + +class FdReceiver : public QObject +{ + Q_OBJECT + +public: + FdReceiver(QObject *parent = nullptr); + + bool startListening(const QString &path); + int fileDescriptor() const; +private: + Q_SLOT void receiveFileDescriptor(); + + QSocketNotifier *m_readNotifier; + int m_socketDes; + int m_fileDes; +}; + +class FdSender +{ +public: + FdSender(); + + void connectToPath(const std::string &path); + bool sendFileDescriptor(int fd); + bool isConnected() const; + +private: + int m_socketDes; + bool m_connected; +}; + +#endif // SHAREFD_H Index: src/ioslaves/file/sharefd.cpp =================================================================== --- /dev/null +++ src/ioslaves/file/sharefd.cpp @@ -0,0 +1,183 @@ +/*** + Copyright (C) 2017 by Chinmoy Ranjan Pradhan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +***/ + +#include +#include +#include +#include +#include "sharefd.h" + +//borrowed from klocalsocket.cpp +class KSockaddrUn +{ + sockaddr_un addr; + int addrlen; + +public: + KSockaddrUn(const std::string &path) + { + addrlen = sizeof(sockaddr_un); + memset(&addr, 0, addrlen); + addr.sun_family = AF_UNIX; + std::string finalPath = "/tmp/" + path; +#ifdef __linux__ + strcpy(&addr.sun_path[1], finalPath.c_str()); +#else + strcpy(&addr.sun_path, finalPath.c_str()); + unlink(finalPath.c_str()); +#endif + } + + int length() const + { + return addrlen; + } + const sockaddr *address() + { + return reinterpret_cast(&addr); + } +}; + +class KMsgHdr +{ + char io_buf[2]; + char cmsg_buf[CMSG_SPACE(sizeof(int))]; + msghdr msg; + iovec io; + +public: + KMsgHdr() + { + io.iov_base = io_buf; + io.iov_len = 2; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + } + + msghdr *message() + { + return &msg; + } + + cmsghdr *cmsgHeader() const + { + return CMSG_FIRSTHDR(&msg); + } +}; + + +// File descriptor reciever +FdReceiver::FdReceiver(QObject *parent) + : QObject(parent) + , m_socketDes(-1) + , m_fileDes(-1) +{ +} + +bool FdReceiver::startListening(const QString &path) +{ + if (path.isEmpty()) + return false; + + m_socketDes = ::socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (m_socketDes == -1) + return false; + + KSockaddrUn addr(path.toStdString()); + bool bound = bind(m_socketDes, addr.address(), addr.length()) != -1; + bool listening = listen(m_socketDes, 5) != -1; + + if (!bound || !listening) { + ::close(m_socketDes); + return false; + } + + m_readNotifier = new QSocketNotifier(m_socketDes, QSocketNotifier::Read, this); + connect(m_readNotifier, &QSocketNotifier::activated, this, &FdReceiver::receiveFileDescriptor); + return true; +} + +void FdReceiver::receiveFileDescriptor() +{ + int client = ::accept(m_socketDes, NULL, NULL); + if (client > 0) { + KMsgHdr msg; + if (recvmsg(client, msg.message(), 0) == 2) { + cmsghdr *cmsg = msg.cmsgHeader(); + memcpy(&m_fileDes, (int*)CMSG_DATA(cmsg), sizeof(int)); + m_readNotifier->setEnabled(false); + } + ::close(client); + } + ::close(m_socketDes); +} + +int FdReceiver::fileDescriptor() const +{ + return m_fileDes; +} + + +// File descriptor sender +FdSender::FdSender() + : m_socketDes(-1) + , m_connected(false) +{ +} + +void FdSender::connectToPath(const std::string &path) +{ + if (path.empty()) + return; + + m_socketDes = ::socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (m_socketDes == -1) + return; + + KSockaddrUn addr(path); + bool connected = ::connect(m_socketDes, addr.address(), addr.length()) == 0; + + if (!connected) + close(m_socketDes); + + m_connected = true; +} + +bool FdSender::sendFileDescriptor(int fd) +{ + KMsgHdr msg; + cmsghdr *cmsg = msg.cmsgHeader(); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_level = SOL_SOCKET; + memcpy((int*)CMSG_DATA(cmsg), &fd, sizeof(int)); + bool success = sendmsg(m_socketDes, msg.message(), 0) == 2; + close(m_socketDes); + return success; +} + +bool FdSender::isConnected() const +{ + return m_connected; +}