File indexing completed on 2024-05-05 04:59:44

0001 /*
0002  * SPDX-FileCopyrightText: 2001 Lucas Fisher <ljfisher@purdue.edu>
0003  * SPDX-FileCopyrightText: 2009 Andreas Schneider <mail@cynapses.org>
0004  * SPDX-FileCopyrightText: 2020-2022 Harald Sitter <sitter@kde.org>
0005  *
0006  * SPDX-License-Identifier: LGPL-2.0-or-later
0007  */
0008 
0009 #ifndef __kio_sftp_h__
0010 #define __kio_sftp_h__
0011 
0012 #include <KIO/Global>
0013 #include <KIO/WorkerBase>
0014 
0015 #include <libssh/callbacks.h>
0016 #include <libssh/libssh.h>
0017 #include <libssh/sftp.h>
0018 
0019 #include <QQueue>
0020 #include <QUrl>
0021 
0022 #include <QCoroGenerator>
0023 
0024 namespace KIO
0025 {
0026 class AuthInfo;
0027 } // namespace KIO
0028 
0029 using Result = KIO::WorkerResult;
0030 
0031 namespace std
0032 {
0033 template<>
0034 struct default_delete<struct sftp_attributes_struct> {
0035     void operator()(struct sftp_attributes_struct *ptr) const
0036     {
0037         sftp_attributes_free(ptr);
0038     }
0039 };
0040 } // namespace std
0041 
0042 using SFTPAttributesPtr = std::unique_ptr<sftp_attributes_struct>;
0043 
0044 class SFTPWorker : public KIO::WorkerBase
0045 {
0046 public:
0047     explicit SFTPWorker(const QByteArray &poolSocket, const QByteArray &appSocket);
0048     ~SFTPWorker() override;
0049     Q_DISABLE_COPY_MOVE(SFTPWorker)
0050 
0051     void setHost(const QString &h, quint16 port, const QString &user, const QString &pass) override;
0052     Q_REQUIRED_RESULT Result get(const QUrl &url) override;
0053     Q_REQUIRED_RESULT Result listDir(const QUrl &url) override;
0054     Q_REQUIRED_RESULT Result mimetype(const QUrl &url) override;
0055     Q_REQUIRED_RESULT Result stat(const QUrl &url) override;
0056     Q_REQUIRED_RESULT Result copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) override;
0057     Q_REQUIRED_RESULT Result put(const QUrl &url, int permissions, KIO::JobFlags flags) override;
0058     void closeConnection() override;
0059     void worker_status() override;
0060     Q_REQUIRED_RESULT Result del(const QUrl &url, bool isfile) override;
0061     Q_REQUIRED_RESULT Result chmod(const QUrl &url, int permissions) override;
0062     Q_REQUIRED_RESULT Result symlink(const QString &target, const QUrl &dest, KIO::JobFlags flags) override;
0063     Q_REQUIRED_RESULT Result rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) override;
0064     Q_REQUIRED_RESULT Result mkdir(const QUrl &url, int permissions) override;
0065     Q_REQUIRED_RESULT Result openConnection() override;
0066 
0067     // KIO::FileJob interface
0068     Q_REQUIRED_RESULT Result open(const QUrl &url, QIODevice::OpenMode mode) override;
0069     Q_REQUIRED_RESULT Result read(KIO::filesize_t size) override;
0070     Q_REQUIRED_RESULT Result write(const QByteArray &data) override;
0071     Q_REQUIRED_RESULT Result seek(KIO::filesize_t offset) override;
0072     Q_REQUIRED_RESULT Result truncate(KIO::filesize_t length) override;
0073     Q_REQUIRED_RESULT Result close() override;
0074     Q_REQUIRED_RESULT Result special(const QByteArray &data) override;
0075 
0076     // libssh authentication callback (note that this is called by the
0077     // global ::auth_callback() call.
0078     int auth_callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata);
0079 
0080     // libssh logging callback (note that this is called by the
0081     // global ::log_callback() call.
0082     void log_callback(int priority, const char *function, const char *buffer, void *userdata);
0083 
0084     // Must call after construction!
0085     // Bit rubbish, but we need to return something on init.
0086     Q_REQUIRED_RESULT Result init();
0087 
0088     Q_REQUIRED_RESULT Result fileSystemFreeSpace(const QUrl &url) override;
0089 
0090 private: // Private variables
0091     /** True if worker is connected to sftp server. */
0092     bool mConnected = false;
0093 
0094     /** Host we are connected to. */
0095     QString mHost;
0096 
0097     /** Port we are connected to. */
0098     int mPort = -1;
0099 
0100     /** The ssh session for the connection */
0101     ssh_session mSession = nullptr;
0102 
0103     /** The sftp session for the connection */
0104     sftp_session mSftp = nullptr;
0105 
0106     /** Username to use when connecting */
0107     QString mUsername;
0108 
0109     /** User's password */
0110     QString mPassword;
0111 
0112     /** The open file */
0113     sftp_file mOpenFile = nullptr;
0114 
0115     /** The open URL */
0116     QUrl mOpenUrl;
0117 
0118     ssh_callbacks mCallbacks = nullptr;
0119 
0120     // KIO::FileJob interface
0121     KIO::filesize_t openOffset = 0;
0122 
0123     /**
0124      * Holds public key authentication info for proper retry handling.
0125      */
0126     KIO::AuthInfo *mPublicKeyAuthInfo = nullptr;
0127 
0128     /**
0129      * GetRequest encapsulates several SFTP get requests into a single object.
0130      * As SFTP messages are limited to MAX_XFER_BUF_SIZE several requests
0131      * should be sent simultaneously in order to increase transfer speeds.
0132      */
0133     class GetRequest
0134     {
0135     public:
0136         /**
0137          * Creates a new GetRequest object.
0138          * Requests do not take ownership of the SFTP pointers! The caller is
0139          * responsible for freeing them.
0140          * @param file the sftp_file object which should be transferred.
0141          * @param size the total size of the file.
0142          * @param maxPendingRequests the maximum number of parallel requests to start with.
0143          *                           The more are pending the higher the potential memory
0144          *                           foot print, however if the connection allows it
0145          *                           we'll get better throughput.
0146          */
0147         GetRequest(sftp_file file, uint64_t size, ushort maxPendingRequests = 128);
0148         /**
0149          * Removes all pending requests and closes the SFTP channel and attributes
0150          * in order to avoid memory leaks.
0151          */
0152         ~GetRequest();
0153         Q_DISABLE_COPY_MOVE(GetRequest)
0154 
0155         /**
0156          * Starts up to maxPendingRequests file requests. Reading is performed in the
0157          * via the readChunks method.
0158          */
0159         bool enqueueChunks();
0160         /**
0161          * Attempts to read all pending chunks in the given QByteArray.
0162          * @param data the array into which the data should be saved (it should be empty).
0163          * @return 0 on EOF or timeout, -1 on error and the number of bytes read otherwise.
0164          */
0165         size_t readChunks(QByteArray &data);
0166 
0167     private:
0168         struct Request {
0169             /** Identifier as returned by the sftp_async_read_begin call */
0170             int id;
0171             /** The number of bytes expected to be returned */
0172             uint32_t expectedLength;
0173             /** The SSH start offset when this request was made */
0174             uint64_t startOffset;
0175         };
0176 
0177         sftp_file m_file;
0178         const uint64_t m_size; // size of file (max readable)
0179         ushort m_maxPendingRequests;
0180         QQueue<Request> m_pendingRequests;
0181     };
0182 
0183     struct ReadResponse {
0184         // NOTE: older GCC versions have trouble when using structs that were aggregate initialized and crash on
0185         // double-destruction problems. If you want to get rid of the ctors make double sure that reasonable GCC
0186         // versions are fine.
0187         ReadResponse() = default;
0188         explicit ReadResponse(const QByteArray &filedata_)
0189             : filedata(filedata_)
0190         {
0191         }
0192         explicit ReadResponse(int error_)
0193             : error(error_)
0194         {
0195         }
0196 
0197         QByteArray filedata;
0198         int error = KJob::NoError;
0199     };
0200     QCoro::Generator<ReadResponse> asyncRead(sftp_file file, size_t size);
0201 
0202     struct WriteResponse {
0203         size_t bytes = 0;
0204         int error = KJob::NoError;
0205     };
0206     QCoro::Generator<WriteResponse> asyncWrite(sftp_file file, QCoro::Generator<ReadResponse> reader);
0207 
0208 private: // private methods
0209     int authenticateKeyboardInteractive(KIO::AuthInfo &info);
0210 
0211     Q_REQUIRED_RESULT Result reportError(const QUrl &url, int err);
0212 
0213     Q_REQUIRED_RESULT Result createUDSEntry(SFTPAttributesPtr sb, KIO::UDSEntry &entry, const QByteArray &path, const QString &name, int details);
0214 
0215     QString canonicalizePath(const QString &path);
0216     void requiresUserNameRedirection();
0217     void clearPubKeyAuthInfo();
0218     Q_REQUIRED_RESULT Result sftpLogin();
0219     Q_REQUIRED_RESULT Result sftpOpenConnection(const KIO::AuthInfo &);
0220 
0221     Q_REQUIRED_RESULT Result sftpGet(const QUrl &url, KIO::fileoffset_t offset = -1, int fd = -1);
0222     Q_REQUIRED_RESULT Result sftpPut(const QUrl &url, int permissions, KIO::JobFlags flags, int fd = -1);
0223 
0224     Q_REQUIRED_RESULT Result sftpCopyGet(const QUrl &url, const QString &src, int permissions, KIO::JobFlags flags);
0225     Q_REQUIRED_RESULT Result sftpCopyPut(const QUrl &url, const QString &dest, int permissions, KIO::JobFlags flags);
0226     Q_REQUIRED_RESULT Result sftpSendMimetype(sftp_file file, const QUrl &url);
0227     Q_REQUIRED_RESULT Result openConnectionWithoutCloseOnError();
0228 };
0229 
0230 #endif