├── qtssh ├── sshkey.cpp ├── sshkey.h ├── sshsftpcommandunlink.h ├── sshsftpcommandmkdir.h ├── sshsftpcommandget.h ├── sshsftpcommandfileinfo.h ├── sshsftpcommandreaddir.h ├── sshsftpcommandsend.h ├── sshsftpcommand.cpp ├── sshprocess.h ├── sshscpget.h ├── sshsftpcommand.h ├── sshscpsend.h ├── sshtunnelinconnection.h ├── sshtunneloutconnection.h ├── sshtunnelout.h ├── sshtunnelin.h ├── sshsftpcommandunlink.cpp ├── sshchannel.h ├── sshsftpcommandmkdir.cpp ├── sshchannel.cpp ├── sshsftp.h ├── sshtunneldataconnector.h ├── sshsftpcommandfileinfo.cpp ├── CMakeLists.txt ├── sshsftpcommandreaddir.cpp ├── sshsftpcommandget.cpp ├── sshsftpcommandsend.cpp ├── sshtunnelinconnection.cpp ├── sshtunnelout.cpp ├── sshtunneloutconnection.cpp ├── sshscpget.cpp ├── sshscpsend.cpp ├── sshtunnelin.cpp ├── sshclient.h ├── sshprocess.cpp ├── sshsftp.cpp └── sshtunneldataconnector.cpp ├── test ├── SshGui │ ├── main.cpp │ ├── mainwindow.h │ ├── mainwindow.cpp │ ├── directtunnelform.h │ ├── sshconnectionform.h │ ├── reversetunnelform.h │ ├── SshGui.pro │ ├── mainwindow.ui │ ├── reversetunnelform.cpp │ ├── directtunnelform.cpp │ ├── directtunnelform.ui │ ├── sshconnectionform.cpp │ ├── reversetunnelform.ui │ └── sshconnectionform.ui ├── SshConsole │ ├── main.cpp │ ├── console.h │ ├── console.cpp │ ├── connection.h │ ├── interactive.pro │ └── connection.cpp └── TestSsh │ ├── testssh.pro │ ├── main.cpp │ └── tester.h ├── .gitignore ├── config.cmake.in ├── CMakeLists.txt ├── LICENSE ├── QtSsh.pri ├── qtssh.cmake ├── doc ├── sshchannel.md ├── sshclient.md ├── sshprocess.md └── installation.md └── README.md /qtssh/sshkey.cpp: -------------------------------------------------------------------------------- 1 | #include "sshkey.h" 2 | 3 | SshKey::SshKey(QObject *parent) 4 | : QObject(parent) 5 | { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /test/SshGui/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | MainWindow w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /test/SshConsole/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "console.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QCoreApplication app(argc, argv); 8 | Console console; 9 | 10 | return app.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | qrc_*.cpp 24 | ui_*.h 25 | Makefile* 26 | *build-* 27 | 28 | # QtCreator 29 | 30 | *.autosave 31 | 32 | #QtCtreator Qml 33 | *.qmlproject.user 34 | *.qmlproject.user.* 35 | -------------------------------------------------------------------------------- /test/SshConsole/console.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSOLE_H 2 | #define CONSOLE_H 3 | 4 | #include 5 | #include 6 | 7 | class Console : public QObject 8 | { 9 | Q_OBJECT 10 | QTcpServer m_server; 11 | int m_instance {0}; 12 | 13 | public: 14 | explicit Console(QObject *parent = nullptr); 15 | 16 | signals: 17 | 18 | public slots: 19 | 20 | private slots: 21 | void _newConnection(); 22 | }; 23 | 24 | #endif // CONSOLE_H 25 | -------------------------------------------------------------------------------- /test/SshConsole/console.cpp: -------------------------------------------------------------------------------- 1 | #include "console.h" 2 | #include "connection.h" 3 | 4 | Console::Console(QObject *parent) : QObject(parent) 5 | { 6 | QObject::connect(&m_server, &QTcpServer::newConnection, this, &Console::_newConnection); 7 | m_server.listen(); 8 | qInfo() << "Listen on " << m_server.serverPort(); 9 | } 10 | 11 | void Console::_newConnection() 12 | { 13 | new Connection(QString("#%1").arg(m_instance++), m_server, this); 14 | } 15 | -------------------------------------------------------------------------------- /qtssh/sshkey.h: -------------------------------------------------------------------------------- 1 | #ifndef SSHKEY_H 2 | #define SSHKEY_H 3 | 4 | #include 5 | 6 | class SshKey : public QObject 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit SshKey(QObject *parent = nullptr); 12 | virtual ~SshKey() = default; 13 | 14 | enum Type { 15 | UnknownType, 16 | Rsa, 17 | Dss 18 | }; 19 | Q_ENUM(Type) 20 | QByteArray hash; 21 | QByteArray key; 22 | Type type {UnknownType}; 23 | }; 24 | 25 | #endif // SSHKEY_H 26 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandunlink.h: -------------------------------------------------------------------------------- 1 | #ifndef SSHSFTPCOMMANDUNLINK_H 2 | #define SSHSFTPCOMMANDUNLINK_H 3 | 4 | #include 5 | #include 6 | 7 | class SshSftpCommandUnlink : public SshSftpCommand 8 | { 9 | Q_OBJECT 10 | const QString &m_path; 11 | bool m_error {false}; 12 | 13 | public: 14 | SshSftpCommandUnlink(const QString &path, SshSFtp &parent); 15 | void process() override; 16 | bool error() const; 17 | }; 18 | 19 | #endif // SSHSFTPCOMMANDUNLINK_H 20 | -------------------------------------------------------------------------------- /test/SshGui/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | QT_BEGIN_NAMESPACE 7 | namespace Ui { class MainWindow; } 8 | QT_END_NAMESPACE 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | MainWindow(QWidget *parent = nullptr); 16 | ~MainWindow(); 17 | 18 | private slots: 19 | void on_pushButton_clicked(); 20 | 21 | private: 22 | Ui::MainWindow *ui; 23 | }; 24 | #endif // MAINWINDOW_H 25 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandmkdir.h: -------------------------------------------------------------------------------- 1 | #ifndef SSHSFTPCOMMANDMKDIR_H 2 | #define SSHSFTPCOMMANDMKDIR_H 3 | 4 | #include 5 | #include 6 | 7 | class SshSftpCommandMkdir : public SshSftpCommand 8 | { 9 | Q_OBJECT 10 | const QString &m_dir; 11 | int m_mode; 12 | bool m_error {false}; 13 | 14 | public: 15 | SshSftpCommandMkdir(const QString &dir, int mode, SshSFtp &parent); 16 | void process() override; 17 | bool error() const; 18 | }; 19 | 20 | #endif // SSHSFTPCOMMANDMKDIR_H 21 | -------------------------------------------------------------------------------- /test/SshGui/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include "sshconnectionform.h" 4 | 5 | MainWindow::MainWindow(QWidget *parent) 6 | : QMainWindow(parent) 7 | , ui(new Ui::MainWindow) 8 | { 9 | ui->setupUi(this); 10 | } 11 | 12 | MainWindow::~MainWindow() 13 | { 14 | delete ui; 15 | } 16 | 17 | 18 | void MainWindow::on_pushButton_clicked() 19 | { 20 | SshConnectionForm *new_ssh = new SshConnectionForm(ui->conname->text(), this); 21 | ui->tabWidget->addTab(new_ssh, ui->conname->text()); 22 | } 23 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandget.h: -------------------------------------------------------------------------------- 1 | #ifndef SSHSFTPCOMMANDGET_H 2 | #define SSHSFTPCOMMANDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class SshSftpCommandGet : public SshSftpCommand 9 | { 10 | Q_OBJECT 11 | 12 | QFile &m_fout; 13 | const QString &m_src; 14 | LIBSSH2_SFTP_HANDLE *m_sftpfile; 15 | bool m_error {false}; 16 | char m_buffer[SFTP_BUFFER_SIZE]; 17 | 18 | public: 19 | SshSftpCommandGet(QFile &fout, const QString &source, SshSFtp &parent); 20 | void process() override; 21 | }; 22 | 23 | #endif // SSHSFTPCOMMANDGET_H 24 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandfileinfo.h: -------------------------------------------------------------------------------- 1 | #ifndef SSHSFTPCOMMANDFILEINFO_H 2 | #define SSHSFTPCOMMANDFILEINFO_H 3 | 4 | #include 5 | #include 6 | 7 | class SshSftpCommandFileInfo : public SshSftpCommand 8 | { 9 | Q_OBJECT 10 | const QString &m_path; 11 | bool m_error {false}; 12 | LIBSSH2_SFTP_ATTRIBUTES m_fileinfo; 13 | 14 | public: 15 | SshSftpCommandFileInfo(const QString &path, SshSFtp &parent); 16 | void process() override; 17 | bool error() const; 18 | LIBSSH2_SFTP_ATTRIBUTES fileinfo() const; 19 | }; 20 | 21 | #endif // SSHSFTPCOMMANDFILEINFO_H 22 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandreaddir.h: -------------------------------------------------------------------------------- 1 | #ifndef SSHSFTPCOMMANDREADDIR_H 2 | #define SSHSFTPCOMMANDREADDIR_H 3 | 4 | #include 5 | #include 6 | 7 | class SshSftpCommandReadDir : public SshSftpCommand 8 | { 9 | Q_OBJECT 10 | const QString &m_dir; 11 | 12 | QStringList m_result; 13 | LIBSSH2_SFTP_HANDLE *m_sftpdir; 14 | char m_buffer[SFTP_BUFFER_SIZE]; 15 | bool m_error {false}; 16 | LIBSSH2_SFTP_ATTRIBUTES m_attrs; 17 | 18 | public: 19 | SshSftpCommandReadDir(const QString &dir, SshSFtp &parent); 20 | void process() override; 21 | QStringList result() const; 22 | }; 23 | 24 | #endif // SSHSFTPCOMMANDREADDIR_H 25 | -------------------------------------------------------------------------------- /test/SshConsole/connection.h: -------------------------------------------------------------------------------- 1 | #ifndef CONNECTION_H 2 | #define CONNECTION_H 3 | 4 | #include 5 | #include 6 | 7 | class QTcpServer; 8 | class SshClient; 9 | 10 | 11 | class Connection : public QObject 12 | { 13 | Q_OBJECT 14 | QScopedPointer m_sock; 15 | SshClient* m_ssh {nullptr}; 16 | QString m_name; 17 | 18 | public: 19 | explicit Connection(QString name, QTcpServer &server, QObject *parent = nullptr); 20 | virtual ~Connection() override; 21 | 22 | signals: 23 | 24 | public slots: 25 | 26 | private slots: 27 | void _input(); 28 | void _sshConnected(); 29 | void _sshDisconnected(); 30 | 31 | private: 32 | void _print(QString msg); 33 | }; 34 | 35 | #endif // CONNECTION_H 36 | -------------------------------------------------------------------------------- /config.cmake.in: -------------------------------------------------------------------------------- 1 | # if sourced in build tree, target "@PROJECT_NAME@" is available 2 | # otherwise include exported targets from the Targets.cmake file 3 | if(NOT TARGET @PROJECT_NAME@) 4 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") 5 | endif() 6 | 7 | # transitively forward dependency to Qt 8 | set(UseQt5 @UseQt5@) 9 | if (UseQt5) 10 | find_package(Qt5 REQUIRED COMPONENTS Widgets Gui Core Network HINTS @Qt5_DIR@) 11 | set(QT_LIBRARIES Qt5::Core) 12 | set(QT_VERSION ${Qt5Core_VERSION}) 13 | else() 14 | find_package(Qt4 REQUIRED COMPONENTS QtGui QtCore QtNetwork) 15 | set(QT_VERSION ${QTVERSION}) 16 | endif() 17 | 18 | 19 | if(NOT @PROJECT_NAME@_FIND_QUIETLY) 20 | message(STATUS "Found @PROJECT_NAME@: ${@PROJECT_NAME@_DIR}") 21 | endif() 22 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandsend.h: -------------------------------------------------------------------------------- 1 | #ifndef SSHSFTPCOMMANDSEND_H 2 | #define SSHSFTPCOMMANDSEND_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class SshSFtp; 10 | 11 | #define SshSftpCommandSend_BUFFER_SIZE 4096 12 | 13 | class SshSftpCommandSend: public SshSftpCommand 14 | { 15 | Q_OBJECT 16 | 17 | QString m_dest; 18 | bool m_error {false}; 19 | 20 | QFile m_localfile; 21 | char m_buffer[SFTP_BUFFER_SIZE]; 22 | char *m_begin {nullptr}; 23 | size_t m_nread {0}; 24 | LIBSSH2_SFTP_HANDLE *m_sftpfile {nullptr}; 25 | 26 | public: 27 | SshSftpCommandSend(const QString &source, QString dest, SshSFtp &parent); 28 | void process() override; 29 | }; 30 | 31 | #endif // SSHSFTPCOMMANDSEND_H 32 | -------------------------------------------------------------------------------- /test/SshGui/directtunnelform.h: -------------------------------------------------------------------------------- 1 | #ifndef DIRECTTUNNELFORM_H 2 | #define DIRECTTUNNELFORM_H 3 | 4 | #include 5 | #include 6 | class SshClient; 7 | class SshTunnelOut; 8 | 9 | namespace Ui { 10 | class DirectTunnelForm; 11 | } 12 | 13 | class DirectTunnelForm : public QWidget 14 | { 15 | Q_OBJECT 16 | SshTunnelOut *m_tunnel; 17 | static int m_counter; 18 | 19 | public: 20 | explicit DirectTunnelForm(SshClient *client, QString hosttarget, QString hostlisten, quint16 port, QWidget *parent = nullptr); 21 | ~DirectTunnelForm(); 22 | 23 | private: 24 | Ui::DirectTunnelForm *ui; 25 | 26 | private slots: 27 | void connectionChanged(int); 28 | void on_closeButton_clicked(); 29 | void stateChanged(SshChannel::ChannelState state); 30 | 31 | signals: 32 | void destroyme(); 33 | }; 34 | 35 | #endif // DIRECTTUNNELFORM_H 36 | -------------------------------------------------------------------------------- /test/SshGui/sshconnectionform.h: -------------------------------------------------------------------------------- 1 | #ifndef SSHCONNECTIONFORM_H 2 | #define SSHCONNECTIONFORM_H 3 | 4 | #include 5 | #include 6 | 7 | 8 | namespace Ui { 9 | class SshConnectionForm; 10 | } 11 | 12 | class DirectTunnelForm; 13 | 14 | class SshConnectionForm : public QWidget 15 | { 16 | Q_OBJECT 17 | SshClient m_client; 18 | 19 | public: 20 | explicit SshConnectionForm(const QString &name, QWidget *parent = nullptr); 21 | ~SshConnectionForm(); 22 | 23 | private slots: 24 | void on_connectButton_clicked(); 25 | 26 | private: 27 | Ui::SshConnectionForm *ui; 28 | 29 | private slots: 30 | void onSshStateChanged(SshClient::SshState sshState); 31 | void on_createDirectTunnelButton_clicked(); 32 | void destroyChannel(); 33 | void on_createReverseTunnelButton_clicked(); 34 | }; 35 | 36 | #endif // SSHCONNECTIONFORM_H 37 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommand.cpp: -------------------------------------------------------------------------------- 1 | #include "sshsftpcommand.h" 2 | 3 | SshSFtp &SshSftpCommand::sftp() const 4 | { 5 | return m_sftp; 6 | } 7 | 8 | void SshSftpCommand::setName(const QString &name) 9 | { 10 | m_name = name; 11 | } 12 | 13 | QString SshSftpCommand::name() const 14 | { 15 | return m_name; 16 | } 17 | 18 | QStringList SshSftpCommand::errMsg() const 19 | { 20 | return m_errMsg; 21 | } 22 | 23 | SshSftpCommand::SshSftpCommand(SshSFtp &sftp) 24 | : QObject(qobject_cast(&sftp)) 25 | , m_sftp(sftp) 26 | { 27 | m_state = CommandState::Openning; 28 | } 29 | 30 | SshSftpCommand::CommandState SshSftpCommand::state() const 31 | { 32 | return m_state; 33 | } 34 | 35 | void SshSftpCommand::setState(const CommandState &state) 36 | { 37 | if(m_state != state) 38 | { 39 | m_state = state; 40 | emit stateChanged(m_state); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/SshGui/reversetunnelform.h: -------------------------------------------------------------------------------- 1 | #ifndef REVERSETUNNELFORM_H 2 | #define REVERSETUNNELFORM_H 3 | 4 | #include 5 | #include 6 | class SshClient; 7 | class SshTunnelIn; 8 | 9 | namespace Ui { 10 | class ReverseTunnelForm; 11 | } 12 | 13 | class ReverseTunnelForm : public QWidget 14 | { 15 | Q_OBJECT 16 | SshTunnelIn *m_tunnel; 17 | static int m_counter; 18 | 19 | public: 20 | explicit ReverseTunnelForm(SshClient *client, quint16 listenPort, QString hostTarget, quint16 hostPort, QWidget *parent = nullptr); 21 | ~ReverseTunnelForm(); 22 | 23 | private: 24 | Ui::ReverseTunnelForm *ui; 25 | 26 | private slots: 27 | void connectionChanged(int); 28 | void on_closeButton_clicked(); 29 | void stateChanged(SshChannel::ChannelState state); 30 | 31 | signals: 32 | void destroyme(ReverseTunnelForm *); 33 | }; 34 | 35 | #endif // REVERSETUNNELFORM_H 36 | -------------------------------------------------------------------------------- /qtssh/sshprocess.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sshchannel.h" 4 | #include 5 | #include 6 | 7 | Q_DECLARE_LOGGING_CATEGORY(logsshprocess) 8 | 9 | class SshProcess : public SshChannel 10 | { 11 | Q_OBJECT 12 | 13 | protected: 14 | explicit SshProcess(const QString &name, SshClient *client); 15 | friend class SshClient; 16 | 17 | public: 18 | virtual ~SshProcess() override; 19 | void close() override; 20 | QByteArray result(); 21 | QStringList errMsg(); 22 | bool isError(); 23 | 24 | public slots: 25 | void runCommand(const QString &cmd); 26 | void sshDataReceived() override; 27 | 28 | private: 29 | QString m_cmd; 30 | LIBSSH2_CHANNEL *m_sshChannel {nullptr}; 31 | QByteArray m_result; 32 | QStringList m_errMsg; 33 | bool m_error {false}; 34 | 35 | signals: 36 | void finished(); 37 | void failed(); 38 | }; 39 | -------------------------------------------------------------------------------- /test/TestSsh/testssh.pro: -------------------------------------------------------------------------------- 1 | QT -= gui 2 | 3 | QT += testlib 4 | 5 | CONFIG += c++1z console testcase 6 | CONFIG -= app_bundle 7 | 8 | # The following define makes your compiler emit warnings if you use 9 | # any Qt feature that has been marked deprecated (the exact warnings 10 | # depend on your compiler). Please consult the documentation of the 11 | # deprecated API in order to know how to port your code away from it. 12 | DEFINES += QT_DEPRECATED_WARNINGS 13 | 14 | # You can also make your code fail to compile if it uses deprecated APIs. 15 | # In order to do so, uncomment the following line. 16 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 17 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 18 | 19 | SOURCES += main.cpp tester.cpp 20 | HEADERS += tester.h 21 | 22 | include(../../QtSsh.pri) 23 | 24 | LIBS += -lssh2 25 | -------------------------------------------------------------------------------- /qtssh/sshscpget.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sshchannel.h" 4 | #include 5 | 6 | Q_DECLARE_LOGGING_CATEGORY(logscpget) 7 | 8 | class SshScpGet : public SshChannel 9 | { 10 | Q_OBJECT 11 | 12 | protected: 13 | SshScpGet(const QString &name, SshClient *client); 14 | friend class SshClient; 15 | 16 | public: 17 | virtual ~SshScpGet() override; 18 | void close() override; 19 | 20 | 21 | 22 | public slots: 23 | void get(const QString &source, const QString &dest); 24 | void sshDataReceived() override; 25 | 26 | private: 27 | QString m_source; 28 | QString m_dest; 29 | libssh2_struct_stat m_fileinfo = {}; 30 | libssh2_struct_stat_size m_got = 0; 31 | LIBSSH2_CHANNEL *m_sshChannel {nullptr}; 32 | bool m_error {false}; 33 | QFile m_file; 34 | 35 | signals: 36 | void finished(); 37 | void failed(); 38 | void progress(qint64 tx, qint64 total); 39 | }; 40 | -------------------------------------------------------------------------------- /test/SshConsole/interactive.pro: -------------------------------------------------------------------------------- 1 | QT -= gui 2 | 3 | QT += testlib 4 | 5 | CONFIG += c++1z console 6 | CONFIG -= app_bundle 7 | 8 | # The following define makes your compiler emit warnings if you use 9 | # any Qt feature that has been marked deprecated (the exact warnings 10 | # depend on your compiler). Please consult the documentation of the 11 | # deprecated API in order to know how to port your code away from it. 12 | DEFINES += QT_DEPRECATED_WARNINGS 13 | 14 | # You can also make your code fail to compile if it uses deprecated APIs. 15 | # In order to do so, uncomment the following line. 16 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 17 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 18 | 19 | SOURCES += main.cpp \ 20 | connection.cpp \ 21 | console.cpp 22 | 23 | 24 | include(../../QtSsh.pri) 25 | 26 | LIBS += -lssh2 27 | 28 | HEADERS += \ 29 | connection.h \ 30 | console.h 31 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommand.h: -------------------------------------------------------------------------------- 1 | #ifndef SSHSFTPCOMMAND_H 2 | #define SSHSFTPCOMMAND_H 3 | 4 | #include 5 | #include "sshsftp.h" 6 | 7 | 8 | #define SFTP_BUFFER_SIZE 4096 9 | 10 | class SshSftpCommand : public QObject 11 | { 12 | Q_OBJECT 13 | 14 | SshSFtp &m_sftp; 15 | QString m_name; 16 | 17 | public: 18 | enum CommandState { 19 | Openning, 20 | Exec, 21 | Closing, 22 | Terminate, 23 | Error 24 | }; 25 | Q_ENUMS(CommandState) 26 | 27 | explicit SshSftpCommand(SshSFtp &sftp); 28 | virtual void process() = 0; 29 | 30 | CommandState state() const; 31 | 32 | void setState(const CommandState &state); 33 | 34 | SshSFtp &sftp() const; 35 | 36 | void setName(const QString &name); 37 | 38 | QString name() const; 39 | 40 | QStringList errMsg() const; 41 | 42 | protected: 43 | CommandState m_state; 44 | QStringList m_errMsg; 45 | 46 | signals: 47 | void stateChanged(CommandState state); 48 | }; 49 | 50 | #endif // SSHSFTPCOMMAND_H 51 | -------------------------------------------------------------------------------- /test/TestSsh/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "tester.h" 6 | 7 | 8 | 9 | static void (*basicMessageHandler)(QtMsgType type, const QMessageLogContext &context, const QString &msg); 10 | 11 | void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) 12 | { 13 | QString newMsg; 14 | newMsg = "\t" + QTime::currentTime().toString("hh:mm:ss:zzz") + ": " + msg; 15 | (*basicMessageHandler)(type, context, newMsg); 16 | } 17 | 18 | 19 | int main(int argc, char *argv[]) 20 | { 21 | //bool ret; 22 | basicMessageHandler = qInstallMessageHandler(myMessageOutput); 23 | QCoreApplication app(argc, argv); 24 | /* 25 | struct sigaction sigIntHandler; 26 | sigIntHandler.sa_handler = my_handler; 27 | sigemptyset(&sigIntHandler.sa_mask); 28 | sigIntHandler.sa_flags = 0; 29 | sigaction(SIGINT, &sigIntHandler, nullptr); 30 | */ 31 | 32 | Tester test("127.0.0.1", "lpctest", "lpctest"); 33 | return QTest::qExec(&test, argc, argv); 34 | } 35 | -------------------------------------------------------------------------------- /qtssh/sshscpsend.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sshchannel.h" 4 | #include 5 | 6 | #if !defined(PAGE_SIZE) 7 | #define PAGE_SIZE (4*1024) 8 | #endif 9 | 10 | Q_DECLARE_LOGGING_CATEGORY(logscpsend) 11 | 12 | class SshScpSend : public SshChannel 13 | { 14 | Q_OBJECT 15 | 16 | protected: 17 | SshScpSend(const QString &name, SshClient * client); 18 | friend class SshClient; 19 | 20 | public: 21 | virtual ~SshScpSend() override; 22 | void close() override; 23 | 24 | public slots: 25 | void send(const QString &source, QString dest); 26 | void sshDataReceived() override; 27 | 28 | 29 | private: 30 | QString m_source; 31 | QString m_dest; 32 | struct stat m_fileinfo = {}; 33 | libssh2_struct_stat_size m_sent = 0; 34 | LIBSSH2_CHANNEL *m_sshChannel {nullptr}; 35 | bool m_error {false}; 36 | QFile m_file; 37 | char m_buffer[PAGE_SIZE]; 38 | qint64 m_dataInBuf {0}; 39 | qint64 m_offset {0}; 40 | 41 | signals: 42 | void finished(); 43 | void failed(); 44 | void progress(qint64 tx, qint64 total); 45 | }; 46 | -------------------------------------------------------------------------------- /qtssh/sshtunnelinconnection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sshchannel.h" 4 | #include 5 | #include 6 | #include "sshtunneldataconnector.h" 7 | 8 | class QTcpSocket; 9 | 10 | #define BUFFER_SIZE (128*1024) 11 | Q_DECLARE_LOGGING_CATEGORY(logsshtunnelinconnection) 12 | 13 | class SshTunnelInConnection : public SshChannel 14 | { 15 | Q_OBJECT 16 | protected: 17 | explicit SshTunnelInConnection(const QString &name, SshClient *client); 18 | friend class SshClient; 19 | 20 | public: 21 | void configure(LIBSSH2_CHANNEL* channel, quint16 port, QString hostname); 22 | virtual ~SshTunnelInConnection() override; 23 | void close() override; 24 | 25 | private: 26 | SshTunnelDataConnector m_connector; 27 | LIBSSH2_CHANNEL *m_sshChannel {nullptr}; 28 | QTcpSocket m_sock; 29 | quint16 m_port; 30 | QString m_hostname; 31 | bool m_error {false}; 32 | 33 | private slots: 34 | void _socketConnected(); 35 | void _eventLoop(); 36 | 37 | public slots: 38 | void sshDataReceived() override; 39 | void flushTx(); 40 | 41 | 42 | signals: 43 | void sendEvent(); 44 | }; 45 | -------------------------------------------------------------------------------- /qtssh/sshtunneloutconnection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "sshchannel.h" 7 | #include "sshtunneldataconnector.h" 8 | 9 | Q_DECLARE_LOGGING_CATEGORY(logsshtunneloutconnection) 10 | Q_DECLARE_LOGGING_CATEGORY(logsshtunneloutconnectiontransfer) 11 | 12 | class SshTunnelOutConnection : public SshChannel 13 | { 14 | Q_OBJECT 15 | protected: 16 | explicit SshTunnelOutConnection(const QString &name, SshClient *client); 17 | friend class SshClient; 18 | 19 | public: 20 | void configure(QTcpServer *server, quint16 remotePort, QString target = "127.0.0.1"); 21 | virtual ~SshTunnelOutConnection() override; 22 | void close() override; 23 | 24 | private: 25 | SshTunnelDataConnector m_connector; 26 | LIBSSH2_CHANNEL *m_sshChannel {nullptr}; 27 | QTcpSocket *m_sock; 28 | QTcpServer *m_server; 29 | quint16 m_port; 30 | QString m_target; 31 | bool m_error {false}; 32 | 33 | private slots: 34 | void _eventLoop(); 35 | 36 | 37 | public slots: 38 | void sshDataReceived() override; 39 | void flushTx(); 40 | 41 | signals: 42 | void sendEvent(); 43 | }; 44 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if(NOT DEFINED PV) 4 | set(PV "1.3.4") 5 | endif(NOT DEFINED PV) 6 | 7 | project(qtssh VERSION ${PV}) 8 | 9 | option(BUILD_STATIC "Build static library" OFF) 10 | option(WITH_EXAMPLES "Build with examples" OFF) 11 | 12 | set(MESSAGE "${PROJECT_NAME}: ${PV}") 13 | 14 | if(BUILD_STATIC) 15 | string(APPEND MESSAGE " static") 16 | else(BUILD_STATIC) 17 | string(APPEND MESSAGE " shared") 18 | endif(BUILD_STATIC) 19 | 20 | add_subdirectory(qtssh) 21 | 22 | if (WITH_EXAMPLES) 23 | add_subdirectory(examples) 24 | endif() 25 | 26 | # create Config.cmake 27 | configure_file(config.cmake.in "${CMAKE_BINARY_DIR}/${PROJECT_NAME}Config.cmake" @ONLY) 28 | 29 | # create ConfigVersion.cmake 30 | include(CMakePackageConfigHelpers) 31 | write_basic_package_version_file( 32 | "${CMAKE_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" 33 | VERSION ${PROJECT_VERSION} 34 | COMPATIBILITY AnyNewerVersion) 35 | 36 | # install the *Config.cmake and *ConfigVersion.cmake 37 | install(FILES 38 | "${CMAKE_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 39 | "${CMAKE_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" 40 | DESTINATION lib/cmake 41 | ) 42 | 43 | message(STATUS ${MESSAGE}) 44 | -------------------------------------------------------------------------------- /qtssh/sshtunnelout.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "sshchannel.h" 5 | #include "sshtunneloutconnection.h" 6 | #include 7 | 8 | Q_DECLARE_LOGGING_CATEGORY(logsshtunnelout) 9 | 10 | class SshTunnelOut : public SshChannel 11 | { 12 | Q_OBJECT 13 | 14 | protected: 15 | explicit SshTunnelOut(const QString &name, SshClient * client); 16 | friend class SshClient; 17 | 18 | public: 19 | virtual ~SshTunnelOut() override; 20 | void close() override; 21 | quint16 localPort(); 22 | quint16 port() const; 23 | 24 | public slots: 25 | void listen(quint16 port, QString hostTarget = "127.0.0.1", QString hostListen = "127.0.0.1"); 26 | void sshDataReceived() override; 27 | int connections(); 28 | void closeAllConnections(); 29 | void connectionStateChanged(); 30 | void flushTx() const; 31 | 32 | private: 33 | QTcpServer m_tcpserver; 34 | quint16 m_port {0}; 35 | int m_connectionCounter {0}; 36 | QString m_hostTarget; 37 | QList m_connection; 38 | 39 | 40 | private slots: 41 | void _createConnection(); 42 | 43 | signals: 44 | void connectionChanged(int); 45 | }; 46 | -------------------------------------------------------------------------------- /qtssh/sshtunnelin.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sshchannel.h" 4 | #include 5 | #include 6 | 7 | class SshTunnelInConnection; 8 | 9 | Q_DECLARE_LOGGING_CATEGORY(logsshtunnelin) 10 | 11 | class SshTunnelIn : public SshChannel 12 | { 13 | Q_OBJECT 14 | 15 | private: 16 | quint16 m_localTcpPort {0}; 17 | quint16 m_remoteTcpPort {0}; 18 | int m_boundPort {0}; 19 | int m_queueSize {16}; 20 | int m_retryListen {10}; 21 | QString m_targethost; 22 | QString m_listenhost; 23 | LIBSSH2_LISTENER *m_sshListener {nullptr}; 24 | int m_connectionCounter {0}; 25 | QList m_connection; 26 | 27 | protected: 28 | explicit SshTunnelIn(const QString &name, SshClient *client); 29 | friend class SshClient; 30 | 31 | public: 32 | virtual ~SshTunnelIn() override; 33 | void listen(QString host, quint16 localPort, quint16 remotePort, QString listenHost = "127.0.0.1", int queueSize = 16); 34 | void close() override; 35 | quint16 localPort(); 36 | quint16 remotePort(); 37 | 38 | public slots: 39 | void sshDataReceived() override; 40 | void connectionStateChanged(); 41 | void flushTx() const; 42 | 43 | signals: 44 | void connectionChanged(int); 45 | }; 46 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandunlink.cpp: -------------------------------------------------------------------------------- 1 | #include "sshsftpcommandunlink.h" 2 | #include "sshclient.h" 3 | 4 | SshSftpCommandUnlink::SshSftpCommandUnlink(const QString &path, SshSFtp &parent) 5 | : SshSftpCommand(parent) 6 | , m_path(path) 7 | { 8 | setName(QString("unlink(%1)").arg(path)); 9 | } 10 | 11 | bool SshSftpCommandUnlink::error() const 12 | { 13 | return m_error; 14 | } 15 | 16 | void SshSftpCommandUnlink::process() 17 | { 18 | int res; 19 | switch(m_state) 20 | { 21 | case Openning: 22 | res = libssh2_sftp_unlink_ex( 23 | sftp().getSftpSession(), 24 | qPrintable(m_path), 25 | static_cast(m_path.size()) 26 | ); 27 | 28 | if(res < 0) 29 | { 30 | if(res == LIBSSH2_ERROR_EAGAIN) 31 | { 32 | return; 33 | } 34 | m_error = true; 35 | m_errMsg << QString("SFTP unlink error: %1").arg(res); 36 | qCWarning(logsshsftp) << "SFTP unlink error " << res; 37 | setState(CommandState::Error); 38 | break; 39 | } 40 | setState(CommandState::Terminate); 41 | FALLTHROUGH; 42 | case Terminate: 43 | break; 44 | 45 | case Error: 46 | break; 47 | 48 | default: 49 | setState(CommandState::Terminate); 50 | break; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/SshGui/SshGui.pro: -------------------------------------------------------------------------------- 1 | QT += core gui 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++17 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | # You can also make your code fail to compile if it uses deprecated APIs. 14 | # In order to do so, uncomment the following line. 15 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 16 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 17 | 18 | SOURCES += \ 19 | directtunnelform.cpp \ 20 | main.cpp \ 21 | mainwindow.cpp \ 22 | sshconnectionform.cpp \ 23 | reversetunnelform.cpp 24 | 25 | HEADERS += \ 26 | directtunnelform.h \ 27 | mainwindow.h \ 28 | sshconnectionform.h \ 29 | reversetunnelform.h 30 | 31 | FORMS += \ 32 | directtunnelform.ui \ 33 | mainwindow.ui \ 34 | sshconnectionform.ui \ 35 | reversetunnelform.ui 36 | 37 | include(../../QtSsh.pri) 38 | 39 | LIBS += "-lssh2" 40 | 41 | 42 | # Default rules for deployment. 43 | qnx: target.path = /tmp/$${TARGET}/bin 44 | else: unix:!android: target.path = /opt/$${TARGET}/bin 45 | !isEmpty(target.path): INSTALLS += target 46 | -------------------------------------------------------------------------------- /qtssh/sshchannel.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file sshchannel.h 3 | * \brief Generic Channel SSH class 4 | * \author Fabien Proriol 5 | * \version 1.0 6 | * \date 2018/03/22 7 | * \details This class is a Generic class for all Ssh Channel 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | class SshClient; 18 | 19 | Q_DECLARE_LOGGING_CATEGORY(sshchannel) 20 | 21 | class SshChannel : public QObject 22 | { 23 | Q_OBJECT 24 | friend class SshClient; 25 | 26 | public: 27 | QString name() const; 28 | virtual void close() = 0; 29 | 30 | enum ChannelState { 31 | Openning, 32 | Exec, 33 | Ready, 34 | Close, 35 | WaitClose, 36 | Freeing, 37 | Free, 38 | Error 39 | }; 40 | Q_ENUM(ChannelState) 41 | 42 | ChannelState channelState() const; 43 | void setChannelState(const ChannelState &channelState); 44 | bool waitForState(SshChannel::ChannelState state); 45 | 46 | SshClient *sshClient() const; 47 | 48 | protected: 49 | explicit SshChannel(QString name, SshClient *client); 50 | virtual ~SshChannel(); 51 | SshClient *m_sshClient {nullptr}; 52 | QString m_name; 53 | 54 | protected slots: 55 | virtual void sshDataReceived() {} 56 | 57 | private: 58 | ChannelState m_channelState {ChannelState::Openning}; 59 | 60 | signals: 61 | void stateChanged(ChannelState state); 62 | }; 63 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandmkdir.cpp: -------------------------------------------------------------------------------- 1 | #include "sshsftpcommandmkdir.h" 2 | #include "sshclient.h" 3 | 4 | SshSftpCommandMkdir::SshSftpCommandMkdir(const QString &dir, int mode, SshSFtp &parent) 5 | : SshSftpCommand(parent) 6 | , m_dir(dir) 7 | , m_mode(mode) 8 | { 9 | setName(QString("mkdir(%1)").arg(dir)); 10 | } 11 | 12 | bool SshSftpCommandMkdir::error() const 13 | { 14 | return m_error; 15 | } 16 | 17 | 18 | void SshSftpCommandMkdir::process() 19 | { 20 | int res; 21 | switch(m_state) 22 | { 23 | case Openning: 24 | res = libssh2_sftp_mkdir_ex( 25 | sftp().getSftpSession(), 26 | qPrintable(m_dir), 27 | static_cast(m_dir.size()), 28 | m_mode 29 | ); 30 | 31 | if(res < 0) 32 | { 33 | if(res == LIBSSH2_ERROR_EAGAIN) 34 | { 35 | return; 36 | } 37 | m_error = true; 38 | m_errMsg << ("SFTP mkdir error " + QString::number(res)); 39 | qCWarning(logsshsftp) << "SFTP mkdir error " << res; 40 | setState(CommandState::Error); 41 | break; 42 | } 43 | setState(CommandState::Terminate); 44 | FALLTHROUGH; 45 | case Terminate: 46 | break; 47 | 48 | case Error: 49 | break; 50 | 51 | default: 52 | setState(CommandState::Terminate); 53 | break; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Proriol Fabien All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /QtSsh.pri: -------------------------------------------------------------------------------- 1 | # QtSsh 2 | QT += core network 3 | 4 | HEADERS += \ 5 | $$PWD/qtssh/sshsftpcommandfileinfo.h \ 6 | $$PWD/qtssh/sshsftpcommandget.h \ 7 | $$PWD/qtssh/sshsftpcommandmkdir.h \ 8 | $$PWD/qtssh/sshsftpcommandreaddir.h \ 9 | $$PWD/qtssh/sshsftpcommandsend.h \ 10 | $$PWD/qtssh/sshsftpcommandunlink.h \ 11 | $$PWD/qtssh/sshtunnelout.h \ 12 | $$PWD/qtssh/sshtunnelin.h \ 13 | $$PWD/qtssh/sshprocess.h \ 14 | $$PWD/qtssh/sshchannel.h \ 15 | $$PWD/qtssh/sshclient.h \ 16 | $$PWD/qtssh/sshscpsend.h \ 17 | $$PWD/qtssh/sshscpget.h \ 18 | $$PWD/qtssh/sshsftp.h \ 19 | $$PWD/qtssh/sshsftpcommand.h \ 20 | $$PWD/qtssh/sshkey.h \ 21 | $$PWD/qtssh/sshtunnelinconnection.h \ 22 | $$PWD/qtssh/sshtunneloutconnection.h \ 23 | $$PWD/qtssh/sshtunneldataconnector.h 24 | 25 | 26 | SOURCES += \ 27 | $$PWD/qtssh/sshsftpcommandfileinfo.cpp \ 28 | $$PWD/qtssh/sshsftpcommandget.cpp \ 29 | $$PWD/qtssh/sshsftpcommandmkdir.cpp \ 30 | $$PWD/qtssh/sshsftpcommandreaddir.cpp \ 31 | $$PWD/qtssh/sshsftpcommandsend.cpp \ 32 | $$PWD/qtssh/sshsftpcommandunlink.cpp \ 33 | $$PWD/qtssh/sshtunnelout.cpp \ 34 | $$PWD/qtssh/sshtunnelin.cpp \ 35 | $$PWD/qtssh/sshprocess.cpp \ 36 | $$PWD/qtssh/sshchannel.cpp \ 37 | $$PWD/qtssh/sshclient.cpp \ 38 | $$PWD/qtssh/sshscpsend.cpp \ 39 | $$PWD/qtssh/sshscpget.cpp \ 40 | $$PWD/qtssh/sshsftp.cpp \ 41 | $$PWD/qtssh/sshsftpcommand.cpp \ 42 | $$PWD/qtssh/sshkey.cpp \ 43 | $$PWD/qtssh/sshtunnelinconnection.cpp \ 44 | $$PWD/qtssh/sshtunneloutconnection.cpp \ 45 | $$PWD/qtssh/sshtunneldataconnector.cpp 46 | 47 | INCLUDEPATH += $$PWD/qtssh 48 | -------------------------------------------------------------------------------- /qtssh/sshchannel.cpp: -------------------------------------------------------------------------------- 1 | #include "sshchannel.h" 2 | #include "sshclient.h" 3 | #include 4 | 5 | Q_LOGGING_CATEGORY(sshchannel, "ssh.channel", QtWarningMsg) 6 | 7 | SshChannel::SshChannel(QString name, SshClient *client) 8 | : QObject(client) 9 | , m_sshClient(client) 10 | , m_name(name) 11 | { 12 | qCDebug(sshchannel) << "createChannel:" << m_name; 13 | QObject::connect(m_sshClient, &SshClient::sshDataReceived, this, &SshChannel::sshDataReceived, Qt::QueuedConnection); 14 | } 15 | 16 | SshChannel::~SshChannel() 17 | { 18 | qCDebug(sshchannel) << "destroyChannel:" << this; 19 | setChannelState(ChannelState::Free); 20 | } 21 | 22 | SshClient *SshChannel::sshClient() const 23 | { 24 | return m_sshClient; 25 | } 26 | 27 | SshChannel::ChannelState SshChannel::channelState() const 28 | { 29 | return m_channelState; 30 | } 31 | 32 | void SshChannel::setChannelState(const ChannelState &channelState) 33 | { 34 | if(m_channelState != channelState) 35 | { 36 | qCDebug(sshchannel) << m_name << "Change State:" << m_channelState << "->" << channelState; 37 | m_channelState = channelState; 38 | emit stateChanged(m_channelState); 39 | } 40 | } 41 | 42 | bool SshChannel::waitForState(SshChannel::ChannelState state) 43 | { 44 | QEventLoop wait(this); 45 | QObject::connect(this, &SshChannel::stateChanged, &wait, &QEventLoop::quit); 46 | while(channelState() != ChannelState::Error && channelState() != ChannelState::Free && channelState() !=state) 47 | { 48 | wait.exec(); 49 | } 50 | return channelState() == state; 51 | } 52 | 53 | 54 | QString SshChannel::name() const 55 | { 56 | return m_name; 57 | } 58 | -------------------------------------------------------------------------------- /qtssh/sshsftp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sshchannel.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class SshSftpCommand; 13 | 14 | Q_DECLARE_LOGGING_CATEGORY(logsshsftp) 15 | 16 | class SshSFtp : public SshChannel 17 | { 18 | Q_OBJECT 19 | 20 | private: 21 | LIBSSH2_SFTP *m_sftpSession {nullptr}; 22 | QString m_mkdir; 23 | bool m_error {false}; 24 | QStringList m_errMsg; 25 | 26 | QList m_cmd; 27 | SshSftpCommand *m_currentCmd {nullptr}; 28 | 29 | QHash m_fileinfo; 30 | LIBSSH2_SFTP_ATTRIBUTES getFileInfo(const QString &path); 31 | 32 | protected: 33 | SshSFtp(const QString &name, SshClient * client); 34 | friend class SshClient; 35 | 36 | public: 37 | virtual ~SshSFtp() override; 38 | void close() override; 39 | 40 | QString send(const QString &source, QString dest); 41 | bool get(const QString &source, QString dest, bool override = false); 42 | int mkdir(const QString &dest, int mode = 0755); 43 | QStringList readdir(const QString &d); 44 | bool isDir(const QString &d); 45 | bool isFile(const QString &d); 46 | int mkpath(const QString &dest); 47 | bool unlink(const QString &d); 48 | quint64 filesize(const QString &d); 49 | 50 | LIBSSH2_SFTP *getSftpSession() const; 51 | bool processCmd(SshSftpCommand *cmd); 52 | 53 | bool isError(); 54 | QStringList errMsg(); 55 | 56 | public slots: 57 | void sshDataReceived() override; 58 | 59 | private slots: 60 | void _eventLoop(); 61 | 62 | signals: 63 | void sendEvent(); 64 | void cmdEvent(); 65 | }; 66 | -------------------------------------------------------------------------------- /test/SshGui/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | connection 25 | 26 | 27 | 28 | 29 | 30 | 31 | New connection 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -1 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 0 51 | 0 52 | 800 53 | 20 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /qtssh/sshtunneldataconnector.h: -------------------------------------------------------------------------------- 1 | #ifndef SSHTUNNELDATACONNECTOR_H 2 | #define SSHTUNNELDATACONNECTOR_H 3 | 4 | #include 5 | #include 6 | #include "sshchannel.h" 7 | class QTcpSocket; 8 | 9 | #define BUFFER_SIZE (128*1024) 10 | 11 | Q_DECLARE_LOGGING_CATEGORY(logxfer) 12 | 13 | class SshTunnelDataConnector : public QObject 14 | { 15 | Q_OBJECT 16 | 17 | SshClient *m_sshClient {nullptr}; 18 | LIBSSH2_CHANNEL *m_sshChannel {nullptr}; 19 | QTcpSocket *m_sock {nullptr}; 20 | QString m_name; 21 | 22 | /* Transfer functions */ 23 | 24 | /* TX Channel */ 25 | char m_tx_buffer[BUFFER_SIZE] {0,}; 26 | char *m_tx_start_ptr {nullptr}; 27 | char *m_tx_stop_ptr {nullptr}; 28 | size_t _txBufferLen() const; 29 | 30 | bool m_tx_data_on_sock {false}; 31 | ssize_t _transferSockToTx(); 32 | ssize_t m_total_sockToTx {0}; 33 | bool m_tx_eof {false}; 34 | 35 | ssize_t _transferTxToSsh(); 36 | ssize_t m_total_TxToSsh {0}; 37 | bool m_tx_closed {false}; 38 | 39 | 40 | /* RX Channel */ 41 | char m_rx_buffer[BUFFER_SIZE] {0,}; 42 | char *m_rx_start_ptr {nullptr}; 43 | char *m_rx_stop_ptr {nullptr}; 44 | size_t _rxBufferLen() const; 45 | 46 | bool m_rx_data_on_ssh {false}; 47 | ssize_t _transferSshToRx(); 48 | ssize_t m_total_SshToRx {0}; 49 | bool m_rx_eof {false}; 50 | 51 | ssize_t _transferRxToSock(); 52 | ssize_t m_total_RxToSock {0}; 53 | bool m_rx_closed {false}; 54 | 55 | 56 | public slots: 57 | void sshDataReceived(); 58 | bool process(); 59 | void close(); 60 | bool isClosed(); 61 | void flushTx(); 62 | 63 | public: 64 | explicit SshTunnelDataConnector(SshClient *client, const QString &name, QObject *parent = nullptr); 65 | virtual ~SshTunnelDataConnector(); 66 | void setChannel(LIBSSH2_CHANNEL *channel); 67 | void setSock(QTcpSocket *sock); 68 | 69 | signals: 70 | void sendEvent(); 71 | void processed(); 72 | 73 | private slots: 74 | void _socketDisconnected(); 75 | void _socketDataRecived(); 76 | void _socketError(); 77 | }; 78 | 79 | #endif // SSHTUNNELDATACONNECTOR_H 80 | -------------------------------------------------------------------------------- /test/SshGui/reversetunnelform.cpp: -------------------------------------------------------------------------------- 1 | #include "reversetunnelform.h" 2 | #include "ui_reversetunnelform.h" 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | int ReverseTunnelForm::m_counter = 0; 9 | 10 | ReverseTunnelForm::ReverseTunnelForm(SshClient *client, quint16 listenPort, QString hostTarget, quint16 portTarget, QWidget *parent) : 11 | QWidget(parent), 12 | ui(new Ui::ReverseTunnelForm) 13 | { 14 | ui->setupUi(this); 15 | m_tunnel = client->getChannel(QString("tunnel_%1").arg(m_counter++)); 16 | m_tunnel->listen(hostTarget, portTarget, listenPort); 17 | QObject::connect(m_tunnel, &SshTunnelIn::connectionChanged, this, &ReverseTunnelForm::connectionChanged); 18 | QObject::connect(m_tunnel, &SshTunnelIn::stateChanged, this, &ReverseTunnelForm::stateChanged); 19 | 20 | ui->txtTarget->setText(QString("%1:%2").arg(hostTarget).arg(portTarget)); 21 | ui->txtConnections->setText("0"); 22 | ui->txtState->setText(QMetaEnum::fromType().valueToKey( int(m_tunnel->channelState()) )); 23 | } 24 | 25 | ReverseTunnelForm::~ReverseTunnelForm() 26 | { 27 | delete ui; 28 | } 29 | 30 | void ReverseTunnelForm::connectionChanged(int connections) 31 | { 32 | ui->txtConnections->setText(QString("%1").arg(connections)); 33 | } 34 | 35 | void ReverseTunnelForm::on_closeButton_clicked() 36 | { 37 | switch(m_tunnel->channelState()) { 38 | case SshChannel::ChannelState::Ready: 39 | m_tunnel->close(); 40 | ui->closeButton->setText("Remove"); 41 | break; 42 | case SshChannel::ChannelState::Free: 43 | qWarning() << "Ask to destroy"; 44 | emit destroyme(this); 45 | break; 46 | default: 47 | qWarning() << "Click remove when state is " << QMetaEnum::fromType().valueToKey( int(m_tunnel->channelState()) ) << "(" << m_tunnel->channelState() << ")"; 48 | break; 49 | } 50 | } 51 | 52 | void ReverseTunnelForm::stateChanged(SshChannel::ChannelState state) 53 | { 54 | ui->txtLocalPort->setText(QString("%1").arg(m_tunnel->remotePort())); 55 | ui->txtState->setText(QMetaEnum::fromType().valueToKey( int(state) )); 56 | } 57 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandfileinfo.cpp: -------------------------------------------------------------------------------- 1 | #include "sshsftpcommandfileinfo.h" 2 | #include "sshclient.h" 3 | 4 | LIBSSH2_SFTP_ATTRIBUTES SshSftpCommandFileInfo::fileinfo() const 5 | { 6 | return m_fileinfo; 7 | } 8 | 9 | SshSftpCommandFileInfo::SshSftpCommandFileInfo(const QString &path, SshSFtp &parent) 10 | : SshSftpCommand(parent) 11 | , m_path(path) 12 | { 13 | setName(QString("unlink(%1)").arg(path)); 14 | } 15 | 16 | 17 | 18 | bool SshSftpCommandFileInfo::error() const 19 | { 20 | return m_error; 21 | } 22 | 23 | void SshSftpCommandFileInfo::process() 24 | { 25 | int res; 26 | switch(m_state) 27 | { 28 | case Openning: 29 | res = libssh2_sftp_stat_ex( 30 | sftp().getSftpSession(), 31 | qPrintable(m_path), 32 | static_cast(m_path.size()), 33 | LIBSSH2_SFTP_STAT, 34 | &m_fileinfo 35 | ); 36 | 37 | if(res < 0) 38 | { 39 | if(res == LIBSSH2_ERROR_EAGAIN) 40 | { 41 | return; 42 | } 43 | m_error = true; 44 | m_errMsg << QString("SFTP unlink error: %1").arg(res); 45 | qCWarning(logsshsftp) << "SFTP unlink error " << res; 46 | if (res == LIBSSH2_ERROR_SFTP_PROTOCOL) 47 | { 48 | int err = libssh2_sftp_last_error(sftp().getSftpSession()); 49 | if (err == LIBSSH2_FX_NO_SUCH_FILE) 50 | { 51 | qCWarning(logsshsftp) << "No such file or directory: " << m_path; 52 | m_errMsg << "No such file or directory: " + m_path; 53 | setState(CommandState::Terminate); 54 | return; 55 | } 56 | else 57 | { 58 | qCWarning(logsshsftp) << "SFTP last error " << err; 59 | m_errMsg << QString("SFTP last error %1").arg(err); 60 | } 61 | } 62 | setState(CommandState::Error); 63 | return; 64 | } 65 | else 66 | { 67 | setState(CommandState::Terminate); 68 | FALLTHROUGH; 69 | } 70 | case Terminate: 71 | break; 72 | 73 | case Error: 74 | break; 75 | 76 | default: 77 | setState(CommandState::Terminate); 78 | break; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/SshGui/directtunnelform.cpp: -------------------------------------------------------------------------------- 1 | #include "directtunnelform.h" 2 | #include "ui_directtunnelform.h" 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | int DirectTunnelForm::m_counter = 0; 9 | 10 | DirectTunnelForm::DirectTunnelForm(SshClient *client, QString hosttarget, QString hostlisten, quint16 port, QWidget *parent) : 11 | QWidget(parent), 12 | ui(new Ui::DirectTunnelForm) 13 | { 14 | ui->setupUi(this); 15 | m_tunnel = client->getChannel(QString("tunnel_%1").arg(m_counter++)); 16 | m_tunnel->listen(port, hosttarget, hostlisten); 17 | QObject::connect(m_tunnel, &SshTunnelOut::connectionChanged, this, &DirectTunnelForm::connectionChanged); 18 | QObject::connect(m_tunnel, &SshTunnelOut::stateChanged, this, &DirectTunnelForm::stateChanged); 19 | QObject::connect(m_tunnel, &SshTunnelOut::destroyed, this, &DirectTunnelForm::destroyme); 20 | 21 | ui->txtLocalPort->setText(QString("%1").arg(m_tunnel->localPort())); 22 | ui->txtRemotePort->setText(QString("%1").arg(m_tunnel->port())); 23 | ui->txtConnections->setText("0"); 24 | ui->txtState->setText(QMetaEnum::fromType().valueToKey( int(m_tunnel->channelState()) )); 25 | } 26 | 27 | DirectTunnelForm::~DirectTunnelForm() 28 | { 29 | delete ui; 30 | } 31 | 32 | void DirectTunnelForm::connectionChanged(int connections) 33 | { 34 | ui->txtConnections->setText(QString("%1").arg(connections)); 35 | if(m_tunnel && connections == 0) 36 | { 37 | stateChanged(m_tunnel->channelState()); 38 | } 39 | } 40 | 41 | void DirectTunnelForm::on_closeButton_clicked() 42 | { 43 | if(!m_tunnel) return; 44 | 45 | switch(m_tunnel->channelState()) { 46 | case SshChannel::ChannelState::Ready: 47 | m_tunnel->close(); 48 | ui->closeButton->setText("Remove"); 49 | break; 50 | case SshChannel::ChannelState::Free: 51 | qWarning() << "Ask to destroy"; 52 | if(m_tunnel->connections() > 0) 53 | { 54 | m_tunnel->closeAllConnections(); 55 | } 56 | break; 57 | default: 58 | qWarning() << "Click remove when state is " << QMetaEnum::fromType().valueToKey( int(m_tunnel->channelState()) ) << "(" << m_tunnel->channelState() << ")"; 59 | break; 60 | } 61 | } 62 | 63 | void DirectTunnelForm::stateChanged(SshChannel::ChannelState state) 64 | { 65 | ui->txtState->setText(QMetaEnum::fromType().valueToKey( int(state) )); 66 | } 67 | -------------------------------------------------------------------------------- /test/SshGui/directtunnelform.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DirectTunnelForm 4 | 5 | 6 | 7 | 0 8 | 0 9 | 708 10 | 91 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | Unconnected 21 | 22 | 23 | 24 | 25 | 26 | 27 | <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Direct Tunnel:</span> Local Port:</p></body></html> 28 | 29 | 30 | 31 | 32 | 33 | 34 | Close 35 | 36 | 37 | 38 | 39 | 40 | 41 | Connections: 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | State: 56 | 57 | 58 | 59 | 60 | 61 | 62 | true 63 | 64 | 65 | 66 | 67 | 68 | 69 | Remote Port: 70 | 71 | 72 | 73 | 74 | 75 | 76 | true 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /qtssh.cmake: -------------------------------------------------------------------------------- 1 | set(QTSSH_FILES 2 | ${CMAKE_CURRENT_LIST_DIR}/qtssh.cmake 3 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunnelout.cpp 4 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunnelin.cpp 5 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshprocess.cpp 6 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshchannel.cpp 7 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshclient.cpp 8 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshkey.cpp 9 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshscpsend.cpp 10 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftp.cpp 11 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunnelout.cpp 12 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunneloutconnection.cpp 13 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunnelin.cpp 14 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandreaddir.cpp 15 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunnelinconnection.cpp 16 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommand.cpp 17 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandsend.cpp 18 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandfileinfo.cpp 19 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandunlink.cpp 20 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandget.cpp 21 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshscpget.cpp 22 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandmkdir.cpp 23 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunneldataconnector.cpp 24 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunnelout.h 25 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunnelin.h 26 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshprocess.h 27 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshchannel.h 28 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshclient.h 29 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshkey.h 30 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshscpsend.h 31 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftp.h 32 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunnelout.h 33 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunneloutconnection.h 34 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunnelin.h 35 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandreaddir.h 36 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunnelinconnection.h 37 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandfileinfo.h 38 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandsend.h 39 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandget.h 40 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandunlink.h 41 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommand.h 42 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshscpget.h 43 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshsftpcommandmkdir.h 44 | ${CMAKE_CURRENT_LIST_DIR}/qtssh/sshtunneldataconnector.h 45 | ) 46 | 47 | include_directories(${CMAKE_CURRENT_LIST_DIR}/qtssh) 48 | 49 | if(NOT DEFINED LIBSSH2_LIBRARIES) 50 | message(STATUS "Search for SSH2") 51 | find_package(PkgConfig REQUIRED) 52 | pkg_search_module(SSH2 REQUIRED libssh2) 53 | endif() 54 | -------------------------------------------------------------------------------- /qtssh/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(QtSsh VERSION 0.1 LANGUAGES CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | set(CMAKE_AUTOMOC ON) 6 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 7 | 8 | find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Network) 9 | find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Network) 10 | set(QT_LIBRARIES Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network) 11 | 12 | 13 | if(WITH_SSH_LIBRARIES) 14 | message("USE External SSH Headers ${WITH_SSH_HEADERS}") 15 | include_directories(${WITH_SSH_HEADERS}) 16 | set(SSH2_LIBRARIES ${WITH_SSH_LIBRARIES}) 17 | else(WITH_SSH_LIBRARIES) 18 | find_package(PkgConfig REQUIRED) 19 | pkg_search_module(SSH2 REQUIRED libssh2) 20 | link_directories(${SSH2_LIBRARY_DIRS}) 21 | endif(WITH_SSH_LIBRARIES) 22 | 23 | set(SOURCES 24 | sshtunnelout.cpp 25 | sshtunnelin.cpp 26 | sshprocess.cpp 27 | sshchannel.cpp 28 | sshclient.cpp 29 | sshkey.cpp 30 | sshscpsend.cpp 31 | sshsftp.cpp 32 | sshtunnelout.cpp 33 | sshtunneloutconnection.cpp 34 | sshtunnelin.cpp 35 | sshtunnelinconnection.cpp 36 | sshsftpcommand.cpp 37 | sshsftpcommandmkdir.cpp 38 | sshsftpcommandunlink.cpp 39 | sshscpget.cpp 40 | sshsftpcommandfileinfo.cpp 41 | sshsftpcommandreaddir.cpp 42 | sshsftpcommandget.cpp 43 | sshsftpcommandsend.cpp 44 | sshtunneldataconnector.cpp 45 | ) 46 | 47 | 48 | set(HEADERS 49 | sshtunnelout.h 50 | sshtunnelin.h 51 | sshprocess.h 52 | sshchannel.h 53 | sshclient.h 54 | sshkey.h 55 | sshscpsend.h 56 | sshsftp.h 57 | sshtunnelout.h 58 | sshtunneloutconnection.h 59 | sshtunnelin.h 60 | sshtunnelinconnection.h 61 | sshsftpcommand.h 62 | sshsftpcommandmkdir.h 63 | sshsftpcommandunlink.h 64 | sshscpget.h 65 | sshsftpcommandfileinfo.h 66 | sshsftpcommandreaddir.h 67 | sshsftpcommandget.h 68 | sshsftpcommandsend.h 69 | sshtunneldataconnector.h 70 | ) 71 | 72 | 73 | if(BUILD_STATIC) 74 | add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) 75 | else(BUILD_STATIC) 76 | add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS}) 77 | endif(BUILD_STATIC) 78 | target_link_libraries(${PROJECT_NAME} PRIVATE ${SSH2_LIBRARIES} ${QT_LIBRARIES}) 79 | 80 | target_include_directories(${PROJECT_NAME} PUBLIC 81 | $ 82 | $) 83 | target_include_directories(${PROJECT_NAME} PUBLIC ${SSH2_INCLUDE_DIRS}) 84 | set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") 85 | target_compile_definitions(${PROJECT_NAME} PUBLIC DEBUG_SSHCLIENT) 86 | 87 | install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets 88 | ARCHIVE DESTINATION lib 89 | LIBRARY DESTINATION lib 90 | RUNTIME DESTINATION bin 91 | PUBLIC_HEADER DESTINATION include/${PROJECT_NAME} 92 | ) 93 | install(EXPORT ${PROJECT_NAME}Targets 94 | DESTINATION lib/cmake 95 | ) 96 | -------------------------------------------------------------------------------- /test/SshGui/sshconnectionform.cpp: -------------------------------------------------------------------------------- 1 | #include "sshconnectionform.h" 2 | #include "ui_sshconnectionform.h" 3 | #include "directtunnelform.h" 4 | #include "reversetunnelform.h" 5 | #include 6 | 7 | 8 | SshConnectionForm::SshConnectionForm(const QString &name, QWidget *parent) 9 | : QWidget(parent) 10 | , m_client(name) 11 | , ui(new Ui::SshConnectionForm) 12 | { 13 | ui->setupUi(this); 14 | QObject::connect(&m_client, &SshClient::sshStateChanged, this, &SshConnectionForm::onSshStateChanged); 15 | QObject::connect(&m_client, &SshClient::channelsChanged, [this](int nb){ ui->channelsCount->setText(QString("%1").arg(nb));}); 16 | } 17 | 18 | SshConnectionForm::~SshConnectionForm() 19 | { 20 | delete ui; 21 | } 22 | 23 | void SshConnectionForm::on_connectButton_clicked() 24 | { 25 | if((m_client.sshState() == SshClient::SshState::Unconnected)) 26 | { 27 | m_client.setPassphrase(ui->passwordEdit->text()); 28 | m_client.connectToHost(ui->loginEdit->text(), ui->hostnameEdit->text(), ui->portEdit->text().toUShort()); 29 | } 30 | else 31 | { 32 | m_client.disconnectFromHost(); 33 | } 34 | } 35 | 36 | void SshConnectionForm::onSshStateChanged(SshClient::SshState sshState) 37 | { 38 | qWarning() << "New SSH State: " << sshState; 39 | ui->sshStateText->setText(QMetaEnum::fromType().valueToKey( int(sshState) )); 40 | ui->connectButton->setText((sshState == SshClient::SshState::Unconnected)?("Connect"):("Disconnect")); 41 | ui->actionTabWidget->setEnabled(sshState == SshClient::SshState::Ready); 42 | } 43 | 44 | void SshConnectionForm::on_createDirectTunnelButton_clicked() 45 | { 46 | if(ui->portInput->text().length() > 0) 47 | { 48 | DirectTunnelForm *form = new DirectTunnelForm(&m_client, ui->hostInput->text(), "0.0.0.0", ui->portInput->text().toUShort(), this); 49 | QObject::connect(form, &DirectTunnelForm::destroyme, this, &SshConnectionForm::destroyChannel); 50 | ui->listTunnels->layout()->addWidget(form); 51 | } 52 | } 53 | 54 | void SshConnectionForm::destroyChannel() 55 | { 56 | QObject *obj = QObject::sender(); 57 | QWidget *ch = qobject_cast(obj); 58 | if(ch) 59 | { 60 | qWarning() << "Remove channel" << ch; 61 | ui->listTunnels->layout()->removeWidget(ch); 62 | ch->deleteLater(); 63 | } 64 | } 65 | 66 | void SshConnectionForm::on_createReverseTunnelButton_clicked() 67 | { 68 | if(ui->rtargetPortInput->text().length() > 0) 69 | { 70 | ReverseTunnelForm *form = new ReverseTunnelForm(&m_client, ui->rListenPortInput->text().toUShort(), ui->rhostInput->text(), ui->rtargetPortInput->text().toUShort(), this); 71 | QObject::connect(form, &ReverseTunnelForm::destroyme, this, &SshConnectionForm::destroyChannel); 72 | ui->listTunnels->layout()->addWidget(form); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/SshGui/reversetunnelform.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ReverseTunnelForm 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1075 10 | 68 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Reverse Tunnel:</span> Listen Port:</p></body></html> 21 | 22 | 23 | 24 | 25 | 26 | 27 | <html><head/><body><p>Target:</p></body></html> 28 | 29 | 30 | 31 | 32 | 33 | 34 | <html><head/><body><p>Connections</p></body></html> 35 | 36 | 37 | 38 | 39 | 40 | 41 | <html><head/><body><p>State:</p></body></html> 42 | 43 | 44 | 45 | 46 | 47 | 48 | Close 49 | 50 | 51 | 52 | 53 | 54 | 55 | true 56 | 57 | 58 | 59 | 60 | 61 | 62 | true 63 | 64 | 65 | 66 | 67 | 68 | 69 | <html><head/><body><p>0</p></body></html> 70 | 71 | 72 | 73 | 74 | 75 | 76 | <html><head/><body><p>Init</p></body></html> 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandreaddir.cpp: -------------------------------------------------------------------------------- 1 | #include "sshsftpcommandreaddir.h" 2 | #include "sshclient.h" 3 | 4 | QStringList SshSftpCommandReadDir::result() const 5 | { 6 | return m_result; 7 | } 8 | 9 | SshSftpCommandReadDir::SshSftpCommandReadDir(const QString &dir, SshSFtp &parent) 10 | : SshSftpCommand(parent) 11 | , m_dir(dir) 12 | { 13 | setName(QString("readdir(%1)").arg(dir)); 14 | } 15 | 16 | void SshSftpCommandReadDir::process() 17 | { 18 | switch(m_state) 19 | { 20 | case Openning: 21 | m_sftpdir = libssh2_sftp_open_ex( 22 | sftp().getSftpSession(), 23 | qPrintable(m_dir), 24 | static_cast(m_dir.size()), 25 | 0, 26 | 0, 27 | LIBSSH2_SFTP_OPENDIR 28 | ); 29 | 30 | if(!m_sftpdir) 31 | { 32 | char *emsg; 33 | int size; 34 | int ret = libssh2_session_last_error(sftp().sshClient()->session(), &emsg, &size, 0); 35 | if(ret == LIBSSH2_ERROR_EAGAIN) 36 | { 37 | return; 38 | } 39 | qCDebug(logsshsftp) << "Can't open SFTP dir " << m_dir << ", " << QString(emsg); 40 | m_error = true; 41 | m_errMsg << "Can't open SFTP dir " + m_dir + ", " + QString(emsg); 42 | setState(CommandState::Error); 43 | return; 44 | } 45 | else 46 | { 47 | setState(CommandState::Exec); 48 | FALLTHROUGH; 49 | } 50 | case Exec: 51 | while(1) 52 | { 53 | ssize_t rc = libssh2_sftp_readdir_ex(m_sftpdir, m_buffer, SFTP_BUFFER_SIZE, nullptr, 0, &m_attrs); 54 | if(rc < 0) 55 | { 56 | if(rc == LIBSSH2_ERROR_EAGAIN) 57 | { 58 | return; 59 | } 60 | qCWarning(logsshsftp) << "SFTP readdir error " << rc; 61 | m_errMsg << QString("SFTP readdir error: %1").arg(rc); 62 | } 63 | else if(rc == 0) 64 | { 65 | // EOF 66 | setState(Closing); 67 | break; 68 | } 69 | else 70 | { 71 | m_result.append(QString(m_buffer)); 72 | } 73 | } 74 | 75 | case Closing: 76 | { 77 | int rc = libssh2_sftp_close_handle(m_sftpdir); 78 | if(rc < 0) 79 | { 80 | if(rc == LIBSSH2_ERROR_EAGAIN) 81 | { 82 | return; 83 | } 84 | qCWarning(logsshsftp) << "SFTP close error " << rc; 85 | m_errMsg << QString("SFTP close error: %1").arg(rc); 86 | setState(CommandState::Error); 87 | } 88 | if(m_error) 89 | { 90 | setState(CommandState::Error); 91 | break; 92 | } 93 | else setState(CommandState::Terminate); 94 | FALLTHROUGH; 95 | } 96 | case Terminate: 97 | break; 98 | 99 | case Error: 100 | break; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test/SshConsole/connection.cpp: -------------------------------------------------------------------------------- 1 | #include "connection.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | Connection::Connection(QString name, QTcpServer &server, QObject *parent) 9 | : QObject(parent) 10 | , m_sock(server.nextPendingConnection()) 11 | , m_name(name) 12 | { 13 | QObject::connect(m_sock.data(), &QTcpSocket::readyRead, this, &Connection::_input); 14 | _print("?: connect @:"); 15 | } 16 | 17 | Connection::~Connection() 18 | { 19 | delete m_ssh; 20 | } 21 | 22 | void Connection::_print(QString msg) 23 | { 24 | m_sock->write(m_name.toUtf8() + "> "); 25 | m_sock->write(msg.toUtf8()); 26 | if(!msg.endsWith("\n")) 27 | { 28 | m_sock->write("\n"); 29 | } 30 | } 31 | 32 | void Connection::_input() 33 | { 34 | QByteArray data = m_sock->readAll().trimmed(); 35 | if(data.startsWith("connect")) 36 | { 37 | auto t = data.split(' '); 38 | if(t.length() != 2) 39 | { 40 | _print("syntax error: connect @:"); 41 | return; 42 | } 43 | auto t1 = t[1].split('@'); 44 | if(t1.length() != 2) 45 | { 46 | _print("syntax error: connect @:"); 47 | return; 48 | } 49 | QString login = t1[0]; 50 | auto t2 = t1[1].split(':'); 51 | if(t2.length() != 2) 52 | { 53 | _print("syntax error: connect @:"); 54 | return; 55 | } 56 | QString hostname = t2[0]; 57 | QString password = t2[1]; 58 | m_ssh = new SshClient(m_name, this); 59 | QObject::connect(m_ssh, &SshClient::sshReady, this, &Connection::_sshConnected); 60 | QObject::connect(m_ssh, &SshClient::sshDisconnected, this, &Connection::_sshDisconnected); 61 | m_ssh->setPassphrase(password); 62 | m_ssh->connectToHost(login, hostname); 63 | } 64 | if(data.startsWith("disconnect")) 65 | { 66 | _print("Ask disconnection"); 67 | m_ssh->disconnectFromHost(); 68 | } 69 | if(data.startsWith("direct")) 70 | { 71 | auto t = data.split(' '); 72 | if(t.length() != 2) 73 | { 74 | _print("?: direct:"); 75 | return; 76 | } 77 | if(m_ssh) 78 | { 79 | SshTunnelOut *out = m_ssh->getChannel(t[1]); 80 | out->listen(t[1].toUShort()); 81 | 82 | _print(QString("Direct tunnel for %1 on port %2").arg(t[1].toUShort()).arg(out->localPort())); 83 | } 84 | } 85 | if(data.startsWith("quit")) 86 | { 87 | QCoreApplication::quit(); 88 | } 89 | if(data.startsWith("exit")) 90 | { 91 | _print("Bye bye"); 92 | m_sock->close(); 93 | deleteLater(); 94 | } 95 | } 96 | 97 | void Connection::_sshConnected() 98 | { 99 | _print("connected"); 100 | _print("?: disconnect"); 101 | _print("?: direct "); 102 | } 103 | 104 | void Connection::_sshDisconnected() 105 | { 106 | _print("disconnected"); 107 | delete m_ssh; 108 | m_ssh = nullptr; 109 | } 110 | -------------------------------------------------------------------------------- /test/TestSsh/tester.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTER_H 2 | #define TESTER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | Q_DECLARE_LOGGING_CATEGORY(testssh) 11 | 12 | class Tester : public QObject 13 | { 14 | Q_OBJECT 15 | 16 | enum direction { 17 | TEST_NONE = 0, 18 | TEST_SRV2CLI = 1, 19 | TEST_CLI2SRV = 2, 20 | TEST_BIDIR = 3 21 | }; 22 | 23 | QString m_hostname; 24 | QString m_login; 25 | QString m_password; 26 | QByteArray m_bigdata; 27 | QByteArray m_readsrv; 28 | QByteArray m_readcli; 29 | QTcpServer m_srv; 30 | QTcpSocket m_cli; 31 | QTcpSocket *m_srvSocket; 32 | QTimer m_timeout; 33 | SshClient m_ssh; 34 | direction m_mode {TEST_NONE}; 35 | QString m_testName; 36 | QByteArray &m_currentDataToUse; 37 | QList m_sizeForDataSet = {1024*1024, 4*1024*1024, 8*1024*1024, 16*1024*1024, 32*1024*1024, 64*1024*1024, 128*1024*1024, 256*1024*1024}; 38 | QList m_sizeForBenchmarkDataSet = {1024*1024*4}; 39 | QMap m_dataSetBySize; 40 | QEventLoop m_waitTestEnd; 41 | QEventLoop m_waitGeneralPurpose; 42 | 43 | public: 44 | explicit Tester(QString hostname, QString login, QString password, QObject *parent = nullptr); 45 | 46 | public slots: 47 | void checkTest(); 48 | void newServerConnection(); 49 | void serverDataReady(); 50 | void clientDataReady(); 51 | void clientConnected(); 52 | 53 | signals: 54 | void endtest(bool res); 55 | void endSubTest(int result); 56 | void serverBufferChange(); 57 | void clientBufferChange(); 58 | void newClientConnected(QTcpSocket *client); 59 | 60 | private: 61 | bool testRemoteProcess(const QString &name); 62 | void findFirstDifference(const QByteArray &buffer); 63 | void compareResults(); 64 | void populateTestData(); 65 | void dumpData(const QString &testFilename, const QByteArray &data); 66 | private slots: 67 | 68 | void initTestCase(); 69 | void test1_RemoteProcess(); 70 | void test2_directTunnelComClientToServer(); 71 | void test3_directTunnelComServerToClient(); 72 | void test4_directTunnelComBothWays(); 73 | void test5_doubleDirectTunnelComBothWays(); 74 | void test6_reverseTunnelComClientToServer(); 75 | void test7_reverseTunnelComServerToClient(); 76 | void test8_reverseTunnelBothWays(); 77 | void test9_doubleReverseTunnelBothWays(); 78 | void test2_directTunnelComClientToServer_data(); 79 | void test3_directTunnelComServerToClient_data(); 80 | void test4_directTunnelComBothWays_data(); 81 | void test5_doubleDirectTunnelComBothWays_data(); 82 | void test6_reverseTunnelComClientToServer_data(); 83 | void test7_reverseTunnelComServerToClient_data(); 84 | void test8_reverseTunnelBothWays_data(); 85 | void test9_doubleReverseTunnelBothWays_data(); 86 | void init(); 87 | void cleanup(); 88 | void test10_DirectAndReverseTunnelBothWays_data(); 89 | void test10_DirectAndReverseTunnelBothWays(); 90 | void benchmark1_directTunnelComClientToServer(); 91 | void benchmark2_directTunnelComServerToClient(); 92 | void benchmark3_directTunnelBothWays(); 93 | void benchmark4_reverseTunnelComClientToServer(); 94 | void benchmark5_reverseTunnelComServerToClient(); 95 | void benchmark6_reverseTunnelBothWays(); 96 | }; 97 | 98 | #endif // TESTER_H 99 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandget.cpp: -------------------------------------------------------------------------------- 1 | #include "sshsftpcommandget.h" 2 | #include "sshclient.h" 3 | 4 | SshSftpCommandGet::SshSftpCommandGet(QFile &fout, const QString &source, SshSFtp &parent) 5 | : SshSftpCommand(parent) 6 | , m_fout(fout) 7 | , m_src(source) 8 | { 9 | setName(QString("get(%1, %2)").arg(source).arg(fout.fileName())); 10 | } 11 | 12 | void SshSftpCommandGet::process() 13 | { 14 | switch(m_state) 15 | { 16 | case Openning: 17 | m_sftpfile = libssh2_sftp_open_ex( 18 | sftp().getSftpSession(), 19 | qPrintable(m_src), 20 | static_cast(m_src.size()), 21 | LIBSSH2_FXF_READ, 22 | 0, 23 | LIBSSH2_SFTP_OPENFILE 24 | ); 25 | 26 | if(!m_sftpfile) 27 | { 28 | char *emsg; 29 | int size; 30 | int ret = libssh2_session_last_error(sftp().sshClient()->session(), &emsg, &size, 0); 31 | if(ret == LIBSSH2_ERROR_EAGAIN) 32 | { 33 | return; 34 | } 35 | qCDebug(logsshsftp) << "Can't open SFTP file " << m_src << ", " << QString(emsg); 36 | m_error = true; 37 | m_errMsg << "Can't open SFTP file " + m_src + ", " + QString(emsg); 38 | setState(CommandState::Error); 39 | return; 40 | } 41 | 42 | if(!m_fout.open(QIODevice::WriteOnly)) 43 | { 44 | m_error = true; 45 | setState(CommandState::Closing); 46 | } 47 | setState(CommandState::Exec); 48 | FALLTHROUGH; 49 | case Exec: 50 | while(1) 51 | { 52 | ssize_t rc = libssh2_sftp_read(m_sftpfile, m_buffer, SFTP_BUFFER_SIZE); 53 | if(rc < 0) 54 | { 55 | if(rc == LIBSSH2_ERROR_EAGAIN) 56 | { 57 | return; 58 | } 59 | qCWarning(logsshsftp) << "SFTP read error " << rc; 60 | m_error = true; 61 | m_errMsg << QString("SFTP read error: %1").arg(rc); 62 | setState(CommandState::Error); 63 | break; 64 | } 65 | else if(rc == 0) 66 | { 67 | // EOF 68 | setState(CommandState::Closing); 69 | break; 70 | } 71 | else 72 | { 73 | char *begin = m_buffer; 74 | while(rc) 75 | { 76 | ssize_t wrc = m_fout.write(begin, rc); 77 | rc -= wrc; 78 | begin += wrc; 79 | } 80 | } 81 | } 82 | 83 | case Closing: 84 | { 85 | if(m_fout.isOpen()) 86 | { 87 | m_fout.close(); 88 | } 89 | int rc = libssh2_sftp_close_handle(m_sftpfile); 90 | if(rc < 0) 91 | { 92 | if(rc == LIBSSH2_ERROR_EAGAIN) 93 | { 94 | return; 95 | } 96 | qCWarning(logsshsftp) << "SFTP close error " << rc; 97 | m_errMsg << QString("SFTP close error: %1").arg(rc); 98 | setState(CommandState::Error); 99 | } 100 | if(m_error) 101 | { 102 | setState(CommandState::Error); 103 | break; 104 | } 105 | else setState(CommandState::Terminate); 106 | FALLTHROUGH; 107 | } 108 | case Terminate: 109 | break; 110 | 111 | case Error: 112 | break; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /qtssh/sshsftpcommandsend.cpp: -------------------------------------------------------------------------------- 1 | #include "sshsftpcommandsend.h" 2 | #include "sshclient.h" 3 | 4 | SshSftpCommandSend::SshSftpCommandSend(const QString &source, QString dest, SshSFtp &parent) 5 | : SshSftpCommand(parent) 6 | , m_dest(dest) 7 | , m_localfile(source) 8 | { 9 | setName(QString("send(%1, %2)").arg(source, dest)); 10 | } 11 | 12 | void SshSftpCommandSend::process() 13 | { 14 | switch(m_state) 15 | { 16 | case Openning: 17 | m_sftpfile = libssh2_sftp_open_ex( 18 | sftp().getSftpSession(), 19 | qPrintable(m_dest), 20 | static_cast(m_dest.size()), 21 | LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC, 22 | LIBSSH2_SFTP_S_IRUSR|LIBSSH2_SFTP_S_IWUSR| LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH, 23 | LIBSSH2_SFTP_OPENFILE 24 | ); 25 | 26 | if(!m_sftpfile) 27 | { 28 | char *emsg; 29 | int size; 30 | int ret = libssh2_session_last_error(sftp().sshClient()->session(), &emsg, &size, 0); 31 | if(ret == LIBSSH2_ERROR_EAGAIN) 32 | { 33 | return; 34 | } 35 | qCDebug(logsshsftp) << "Can't open SFTP file " << m_dest << ", " << QString(emsg); 36 | m_error = true; 37 | m_errMsg << "Can't open SFTP file " + m_dest + ", " + QString(emsg); 38 | setState(CommandState::Error); 39 | return; 40 | } 41 | 42 | if(!m_localfile.open(QIODevice::ReadOnly)) 43 | { 44 | qCWarning(logsshsftp) << "Can't open local file " << m_localfile.fileName(); 45 | m_error = true; 46 | m_errMsg << "Can't open local file " + m_localfile.fileName(); 47 | setState(CommandState::Closing); 48 | break; 49 | } 50 | setState(CommandState::Exec); 51 | FALLTHROUGH; 52 | case Exec: 53 | while(1) 54 | { 55 | if(m_nread == 0 && m_localfile.isOpen()) 56 | { 57 | m_nread = m_localfile.read(m_buffer, SFTP_BUFFER_SIZE); 58 | if (m_nread <= 0) { 59 | /* end of file */ 60 | setState(CommandState::Closing); 61 | break; 62 | } 63 | m_begin = m_buffer; 64 | } 65 | while(m_nread != 0) 66 | { 67 | ssize_t rc = libssh2_sftp_write(m_sftpfile, m_begin, m_nread); 68 | if(rc < 0) 69 | { 70 | if(rc == LIBSSH2_ERROR_EAGAIN) 71 | { 72 | return; 73 | } 74 | qCWarning(logsshsftp) << "SFTP Write error " << rc; 75 | } 76 | m_nread -= rc; 77 | m_begin += rc; 78 | } 79 | } 80 | break; 81 | 82 | case Closing: 83 | { 84 | if(m_localfile.isOpen()) 85 | { 86 | m_localfile.close(); 87 | } 88 | int rc = libssh2_sftp_close_handle(m_sftpfile); 89 | if(rc < 0) 90 | { 91 | if(rc == LIBSSH2_ERROR_EAGAIN) 92 | { 93 | return; 94 | } 95 | qCWarning(logsshsftp) << "SFTP close error " << rc; 96 | m_errMsg << QString("SFTP close error: %1").arg(rc); 97 | setState(CommandState::Error); 98 | } 99 | if(m_error) 100 | { 101 | setState(CommandState::Error); 102 | break; 103 | } 104 | else setState(CommandState::Terminate); 105 | FALLTHROUGH; 106 | } 107 | case Terminate: 108 | break; 109 | 110 | case Error: 111 | break; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /qtssh/sshtunnelinconnection.cpp: -------------------------------------------------------------------------------- 1 | #include "sshtunnelinconnection.h" 2 | #include "sshclient.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Q_LOGGING_CATEGORY(logsshtunnelinconnection, "ssh.tunnelin.connection", QtWarningMsg) 10 | 11 | #define BUFFER_LEN (16384) 12 | #define DEBUGCH qCDebug(logsshtunnelinconnection) << m_name 13 | 14 | 15 | 16 | 17 | SshTunnelInConnection::SshTunnelInConnection(const QString &name, SshClient *client) 18 | : SshChannel(name, client) 19 | , m_connector(client, name) 20 | { 21 | QObject::connect(&m_sock, &QTcpSocket::connected, this, &SshTunnelInConnection::_socketConnected); 22 | QObject::connect(this, &SshTunnelInConnection::sendEvent, this, &SshTunnelInConnection::_eventLoop, Qt::QueuedConnection); 23 | QObject::connect(&m_connector, &SshTunnelDataConnector::sendEvent, this, &SshTunnelInConnection::sendEvent); 24 | } 25 | 26 | void SshTunnelInConnection::configure(LIBSSH2_CHANNEL *channel, quint16 port, QString hostname) 27 | { 28 | DEBUGCH << "configure: " << hostname << ":" << port; 29 | m_sshChannel = channel; 30 | m_port = port; 31 | m_hostname = hostname; 32 | _eventLoop(); 33 | } 34 | 35 | SshTunnelInConnection::~SshTunnelInConnection() 36 | { 37 | DEBUGCH << "SshTunnelInConnection Destroyed"; 38 | } 39 | 40 | void SshTunnelInConnection::close() 41 | { 42 | } 43 | 44 | void SshTunnelInConnection::_eventLoop() 45 | { 46 | switch(channelState()) 47 | { 48 | case Openning: 49 | { 50 | DEBUGCH << "Channel session opened:" << m_hostname << ":" << m_port; 51 | m_sock.connectToHost(m_hostname, m_port); 52 | setChannelState(SshChannel::Exec); 53 | return; 54 | } 55 | 56 | case Exec: 57 | { 58 | // Wait connected, state change by _socketConnected 59 | return; 60 | } 61 | case Ready: 62 | { 63 | if(!m_connector.process()) 64 | { 65 | setChannelState(ChannelState::Close); 66 | } 67 | return; 68 | } 69 | 70 | case Close: 71 | { 72 | m_connector.close(); 73 | setChannelState(ChannelState::WaitClose); 74 | } 75 | 76 | FALLTHROUGH; case WaitClose: 77 | { 78 | DEBUGCH << "Wait close channel"; 79 | if(m_connector.isClosed()) 80 | { 81 | setChannelState(ChannelState::Freeing); 82 | } 83 | } 84 | 85 | FALLTHROUGH; case Freeing: 86 | { 87 | DEBUGCH << "Freeing Channel"; 88 | 89 | int ret = libssh2_channel_free(m_sshChannel); 90 | if(ret == LIBSSH2_ERROR_EAGAIN) 91 | { 92 | return; 93 | } 94 | if(ret < 0) 95 | { 96 | if(!m_error) 97 | { 98 | m_error = true; 99 | qCWarning(logsshtunnelinconnection) << "Failed to free channel: " << sshErrorToString(ret); 100 | } 101 | } 102 | if(m_error) 103 | { 104 | setChannelState(ChannelState::Error); 105 | } 106 | else 107 | { 108 | setChannelState(ChannelState::Free); 109 | } 110 | m_sshChannel = nullptr; 111 | QObject::disconnect(m_sshClient, &SshClient::sshDataReceived, this, &SshTunnelInConnection::sshDataReceived); 112 | return; 113 | } 114 | 115 | case Free: 116 | { 117 | qCDebug(logsshtunnelinconnection) << "Channel" << m_name << "is free"; 118 | return; 119 | } 120 | 121 | case Error: 122 | { 123 | qCDebug(logsshtunnelinconnection) << "Channel" << m_name << "is in error state"; 124 | return; 125 | } 126 | } 127 | } 128 | 129 | void SshTunnelInConnection::sshDataReceived() 130 | { 131 | DEBUGCH << "sshDataReceived: SSH data received"; 132 | m_connector.sshDataReceived(); 133 | emit sendEvent(); 134 | } 135 | 136 | void SshTunnelInConnection::flushTx() 137 | { 138 | m_connector.flushTx(); 139 | } 140 | 141 | void SshTunnelInConnection::_socketConnected() 142 | { 143 | DEBUGCH << "Socket connection established"; 144 | m_connector.setChannel(m_sshChannel); 145 | m_connector.setSock(&m_sock); 146 | setChannelState(ChannelState::Ready); 147 | emit sendEvent(); 148 | } 149 | 150 | -------------------------------------------------------------------------------- /qtssh/sshtunnelout.cpp: -------------------------------------------------------------------------------- 1 | #include "sshtunnelout.h" 2 | #include "sshclient.h" 3 | #include 4 | 5 | Q_LOGGING_CATEGORY(logsshtunnelout, "ssh.tunnelout", QtWarningMsg) 6 | 7 | 8 | SshTunnelOut::SshTunnelOut(const QString &name, SshClient *client) 9 | : SshChannel(name, client) 10 | { 11 | QObject::connect(&m_tcpserver, &QTcpServer::newConnection, this, &SshTunnelOut::_createConnection); 12 | sshDataReceived(); 13 | } 14 | 15 | SshTunnelOut::~SshTunnelOut() 16 | { 17 | qCDebug(logsshtunnelout) << "delete SshTunnelOut:" << m_name; 18 | } 19 | 20 | void SshTunnelOut::close() 21 | { 22 | qCDebug(logsshtunnelout) << m_name << "Ask to close"; 23 | setChannelState(ChannelState::Close); 24 | sshDataReceived(); 25 | } 26 | 27 | void SshTunnelOut::listen(quint16 port, QString hostTarget, QString hostListen) 28 | { 29 | m_port = port; 30 | m_hostTarget = hostTarget; 31 | m_tcpserver.listen(QHostAddress(hostListen), 0); 32 | setChannelState(ChannelState::Ready); 33 | } 34 | 35 | void SshTunnelOut::sshDataReceived() 36 | { 37 | switch(channelState()) 38 | { 39 | case Openning: 40 | { 41 | qCDebug(logsshtunnelout) << "Channel session opened"; 42 | setChannelState(ChannelState::Exec); 43 | } 44 | 45 | FALLTHROUGH; case Exec: 46 | { 47 | /* OK, next step */ 48 | setChannelState(ChannelState::Ready); 49 | FALLTHROUGH; 50 | } 51 | 52 | case Ready: 53 | { 54 | // Nothing to do... 55 | return; 56 | } 57 | 58 | case Close: 59 | { 60 | qCDebug(logsshtunnelout) << m_name << "Close server"; 61 | m_tcpserver.close(); 62 | setChannelState(ChannelState::WaitClose); 63 | } 64 | 65 | FALLTHROUGH; case WaitClose: 66 | { 67 | qCDebug(logsshtunnelout) << "Wait close channel:" << m_name << " (connections:"<< m_connection.count() << ")"; 68 | if(m_connection.count() == 0) 69 | { 70 | setChannelState(ChannelState::Freeing); 71 | } 72 | else 73 | { 74 | break; 75 | } 76 | } 77 | 78 | FALLTHROUGH; case Freeing: 79 | { 80 | qCDebug(logsshtunnelout) << "free Channel:" << m_name; 81 | setChannelState(ChannelState::Free); 82 | 83 | QObject::disconnect(m_sshClient, &SshClient::sshDataReceived, this, &SshTunnelOut::sshDataReceived); 84 | return; 85 | } 86 | 87 | case Free: 88 | { 89 | qCDebug(logsshtunnelout) << "Channel" << m_name << "is free"; 90 | return; 91 | } 92 | 93 | case Error: 94 | { 95 | qCDebug(logsshtunnelout) << "Channel" << m_name << "is in error state"; 96 | return; 97 | } 98 | } 99 | } 100 | 101 | int SshTunnelOut::connections() 102 | { 103 | return m_connection.count(); 104 | } 105 | 106 | void SshTunnelOut::closeAllConnections() 107 | { 108 | for(SshTunnelOutConnection *connection : m_connection) 109 | { 110 | connection->close(); 111 | } 112 | } 113 | 114 | void SshTunnelOut::connectionStateChanged() 115 | { 116 | QObject *obj = QObject::sender(); 117 | SshTunnelOutConnection *connection = qobject_cast(obj); 118 | if(connection) 119 | { 120 | if(connection->channelState() == SshChannel::ChannelState::Free) 121 | { 122 | m_connection.removeAll(connection); 123 | emit connectionChanged(m_connection.count()); 124 | 125 | if(m_connection.count() == 0 && channelState() == SshChannel::ChannelState::WaitClose) 126 | { 127 | setChannelState(SshChannel::ChannelState::Freeing); 128 | } 129 | } 130 | } 131 | } 132 | 133 | void SshTunnelOut::flushTx() const 134 | { 135 | for(auto &c: m_connection) 136 | { 137 | c->flushTx(); 138 | } 139 | } 140 | 141 | quint16 SshTunnelOut::port() const 142 | { 143 | return m_port; 144 | } 145 | 146 | void SshTunnelOut::_createConnection() 147 | { 148 | qCDebug(logsshtunnelout) << "SshTunnelOut new connection"; 149 | SshTunnelOutConnection *connection = m_sshClient->getChannel(m_name + QString("_%1").arg(m_connectionCounter++)); 150 | connection->configure(&m_tcpserver, m_port, m_hostTarget); 151 | m_connection.append(connection); 152 | QObject::connect(connection, &SshTunnelOutConnection::stateChanged, this, &SshTunnelOut::connectionStateChanged); 153 | emit connectionChanged(m_connection.count()); 154 | } 155 | 156 | quint16 SshTunnelOut::localPort() 157 | { 158 | return m_tcpserver.serverPort(); 159 | } 160 | -------------------------------------------------------------------------------- /doc/sshchannel.md: -------------------------------------------------------------------------------- 1 | # SshChannel API Documentation 2 | 3 | ## Overview 4 | 5 | `SshChannel` is the abstract base class for all SSH channel types in QtSsh. It provides common functionality and state management for SSH operations. All specific channel types (process execution, file transfer, tunneling) inherit from this class. 6 | 7 | ## Class Hierarchy 8 | 9 | ``` 10 | QObject 11 | └── SshChannel (abstract) 12 | ├── SshProcess 13 | ├── SshSFtp 14 | ├── SshScpGet 15 | ├── SshScpSend 16 | ├── SshTunnelIn 17 | └── SshTunnelOut 18 | ``` 19 | 20 | ## Header 21 | 22 | ```cpp 23 | #include "sshchannel.h" 24 | ``` 25 | 26 | ## Constructor 27 | 28 | ```cpp 29 | explicit SshChannel(QString name, SshClient *client) 30 | ``` 31 | 32 | This constructor is protected and called by derived classes. 33 | 34 | **Parameters:** 35 | - `name`: Unique identifier for the channel 36 | - `client`: Pointer to the parent SshClient 37 | 38 | ## Methods 39 | 40 | ### name 41 | 42 | ```cpp 43 | QString name() const 44 | ``` 45 | 46 | Returns the name of this channel. 47 | 48 | **Returns:** Channel name 49 | 50 | ### close 51 | 52 | ```cpp 53 | virtual void close() = 0 54 | ``` 55 | 56 | Pure virtual function to close the channel. Must be implemented by derived classes. 57 | 58 | ### channelState 59 | 60 | ```cpp 61 | ChannelState channelState() const 62 | ``` 63 | 64 | Returns the current state of the channel. 65 | 66 | **Returns:** Current channel state 67 | 68 | ### setChannelState 69 | 70 | ```cpp 71 | void setChannelState(const ChannelState &channelState) 72 | ``` 73 | 74 | Sets the channel state (typically used internally). 75 | 76 | **Parameters:** 77 | - `channelState`: New channel state 78 | 79 | ### waitForState 80 | 81 | ```cpp 82 | bool waitForState(SshChannel::ChannelState state) 83 | ``` 84 | 85 | Blocks until the channel reaches the specified state. 86 | 87 | **Parameters:** 88 | - `state`: Target state to wait for 89 | 90 | **Returns:** `true` if state was reached, `false` on timeout 91 | 92 | **Example:** 93 | ```cpp 94 | SshProcess *proc = client->getChannel("myProc"); 95 | proc->runCommand("ls"); 96 | if (proc->waitForState(SshChannel::Ready)) { 97 | // Command completed 98 | } 99 | ``` 100 | 101 | ### sshClient 102 | 103 | ```cpp 104 | SshClient *sshClient() const 105 | ``` 106 | 107 | Returns a pointer to the parent SSH client. 108 | 109 | **Returns:** Pointer to SshClient 110 | 111 | ## Channel States 112 | 113 | The `ChannelState` enum defines the lifecycle of a channel: 114 | 115 | ```cpp 116 | enum ChannelState { 117 | Openning, // Channel is being opened 118 | Exec, // Executing operation 119 | Ready, // Operation complete, ready for new operations 120 | Close, // Channel is closing 121 | WaitClose, // Waiting for close confirmation 122 | Freeing, // Freeing resources 123 | Free, // Channel is freed 124 | Error // Error occurred 125 | } 126 | ``` 127 | 128 | ## Signals 129 | 130 | ### stateChanged 131 | 132 | ```cpp 133 | void stateChanged(ChannelState state) 134 | ``` 135 | 136 | Emitted when the channel state changes. 137 | 138 | **Parameters:** 139 | - `state`: New channel state 140 | 141 | **Example:** 142 | ```cpp 143 | connect(channel, &SshChannel::stateChanged, [](SshChannel::ChannelState state) { 144 | qDebug() << "Channel state:" << state; 145 | }); 146 | ``` 147 | 148 | ## Protected Slots 149 | 150 | ### sshDataReceived 151 | 152 | ```cpp 153 | virtual void sshDataReceived() 154 | ``` 155 | 156 | Virtual slot called when SSH data is received. Derived classes override this to handle incoming data. 157 | 158 | ## Usage Pattern 159 | 160 | Channels are typically created using `SshClient::getChannel()`: 161 | 162 | ```cpp 163 | // Create or get a process channel 164 | SshProcess *proc = client->getChannel("process1"); 165 | 166 | // Create or get an SFTP channel 167 | SshSFtp *sftp = client->getChannel("sftp1"); 168 | ``` 169 | 170 | ## Channel Lifecycle 171 | 172 | 1. **Creation**: Channel is created via `SshClient::getChannel()` 173 | 2. **Opening**: Channel state is `Openning` 174 | 3. **Operation**: State changes to `Exec` during operation 175 | 4. **Ready**: State becomes `Ready` when operation completes 176 | 5. **Reuse**: Channel can be reused for multiple operations 177 | 6. **Close**: Call `close()` to close the channel 178 | 7. **Cleanup**: State transitions through `Close` → `WaitClose` → `Freeing` → `Free` 179 | 180 | ## Example: Monitoring Channel State 181 | 182 | ```cpp 183 | SshClient *client = new SshClient("myClient"); 184 | client->connectToHost("user", "host.com"); 185 | client->waitForState(SshClient::Ready); 186 | 187 | // Get a channel 188 | SshProcess *proc = client->getChannel("monitor"); 189 | 190 | // Monitor state changes 191 | QObject::connect(proc, &SshChannel::stateChanged, 192 | [](SshChannel::ChannelState state) { 193 | switch(state) { 194 | case SshChannel::Openning: 195 | qDebug() << "Opening channel..."; 196 | break; 197 | case SshChannel::Exec: 198 | qDebug() << "Executing..."; 199 | break; 200 | case SshChannel::Ready: 201 | qDebug() << "Ready!"; 202 | break; 203 | case SshChannel::Error: 204 | qDebug() << "Error occurred"; 205 | break; 206 | default: 207 | break; 208 | } 209 | }); 210 | 211 | proc->runCommand("echo Hello"); 212 | ``` 213 | 214 | ## Best Practices 215 | 216 | 1. **Channel Reuse**: Channels can be reused for multiple operations to reduce overhead 217 | 2. **State Monitoring**: Connect to `stateChanged` signal to track operation progress 218 | 3. **Error Handling**: Always check for `Error` state 219 | 4. **Cleanup**: Call `close()` when done with a channel 220 | 5. **Naming**: Use descriptive names for channels to aid debugging 221 | 222 | ## Thread Safety 223 | 224 | Channels are not thread-safe and should be used from the same thread as their parent `SshClient`. Use Qt's signal/slot mechanism for cross-thread communication. 225 | 226 | ## See Also 227 | 228 | - [SshClient](sshclient.md) - Main client class 229 | - [SshProcess](sshprocess.md) - Process execution channel 230 | - [SshSFtp](sshsftp.md) - SFTP channel 231 | - [SSH Tunneling](tunneling.md) - Tunneling channels 232 | -------------------------------------------------------------------------------- /qtssh/sshtunneloutconnection.cpp: -------------------------------------------------------------------------------- 1 | #include "sshtunneloutconnection.h" 2 | #include "sshtunnelout.h" 3 | #include "sshclient.h" 4 | 5 | Q_LOGGING_CATEGORY(logsshtunneloutconnection, "ssh.tunnelout.connection") 6 | Q_LOGGING_CATEGORY(logsshtunneloutconnectiontransfer, "ssh.tunnelout.connection.transfer") 7 | 8 | #define DEBUGCH qCDebug(logsshtunneloutconnection) << m_name 9 | #define DEBUGTX qCDebug(logsshtunneloutconnectiontransfer) << m_name 10 | 11 | #define SOCKET_WRITE_ERROR (-1001) 12 | 13 | SshTunnelOutConnection::SshTunnelOutConnection(const QString &name, SshClient *client) 14 | : SshChannel(name, client) 15 | , m_connector(client, name) 16 | { 17 | QObject::connect(this, &SshTunnelOutConnection::sendEvent, this, &SshTunnelOutConnection::_eventLoop, Qt::QueuedConnection); 18 | QObject::connect(&m_connector, &SshTunnelDataConnector::sendEvent, this, &SshTunnelOutConnection::sendEvent); 19 | DEBUGCH << "Create SshTunnelOutConnection (constructor)"; 20 | emit sendEvent(); 21 | } 22 | 23 | void SshTunnelOutConnection::configure(QTcpServer *server, quint16 remotePort, QString target) 24 | { 25 | m_server = server; 26 | m_port = remotePort; 27 | m_target = target; 28 | } 29 | 30 | SshTunnelOutConnection::~SshTunnelOutConnection() 31 | { 32 | DEBUGCH << "Free SshTunnelOutConnection (destructor)"; 33 | delete m_sock; 34 | } 35 | 36 | void SshTunnelOutConnection::close() 37 | { 38 | DEBUGCH << "Close SshTunnelOutConnection asked"; 39 | if(channelState() != ChannelState::Error) 40 | { 41 | setChannelState(ChannelState::Close); 42 | } 43 | emit sendEvent(); 44 | } 45 | 46 | 47 | void SshTunnelOutConnection::sshDataReceived() 48 | { 49 | m_connector.sshDataReceived(); 50 | emit sendEvent(); 51 | } 52 | 53 | void SshTunnelOutConnection::flushTx() 54 | { 55 | m_connector.flushTx(); 56 | } 57 | 58 | void SshTunnelOutConnection::_eventLoop() 59 | { 60 | switch(channelState()) 61 | { 62 | case Openning: 63 | { 64 | if ( ! m_sshClient->takeChannelCreationMutex(this) ) 65 | { 66 | return; 67 | } 68 | m_sshChannel = libssh2_channel_direct_tcpip(m_sshClient->session(), qPrintable(m_target), m_port); 69 | m_sshClient->releaseChannelCreationMutex(this); 70 | if (m_sshChannel == nullptr) 71 | { 72 | char *emsg; 73 | int size; 74 | int ret = libssh2_session_last_error(m_sshClient->session(), &emsg, &size, 0); 75 | if(ret == LIBSSH2_ERROR_EAGAIN) 76 | { 77 | return; 78 | } 79 | if(!m_error) 80 | { 81 | qCDebug(logsshtunneloutconnection) << "Refuse client socket connection on " << m_server->serverPort() << QString(emsg); 82 | m_error = true; 83 | m_sock = m_server->nextPendingConnection(); 84 | if(m_sock) 85 | { 86 | m_sock->close(); 87 | } 88 | m_server->close(); 89 | } 90 | setChannelState(ChannelState::Error); 91 | qCWarning(logsshtunneloutconnection) << "Channel session open failed"; 92 | return; 93 | } 94 | DEBUGCH << "Channel session opened"; 95 | setChannelState(ChannelState::Exec); 96 | } 97 | 98 | FALLTHROUGH; case Exec: 99 | { 100 | m_sock = m_server->nextPendingConnection(); 101 | if(!m_sock) 102 | { 103 | m_server->close(); 104 | setChannelState(ChannelState::Error); 105 | qCWarning(logsshtunneloutconnection) << "Fail to get client socket"; 106 | setChannelState(ChannelState::Close); 107 | return; 108 | } 109 | 110 | QObject::connect(m_sock, &QObject::destroyed, [this](){ DEBUGCH << "Client Socket destroyed";}); 111 | m_name = QString(m_name + ":%1").arg(m_sock->localPort()); 112 | DEBUGCH << "createConnection: " << m_sock << m_sock->localPort(); 113 | m_connector.setChannel(m_sshChannel); 114 | m_connector.setSock(m_sock); 115 | setChannelState(ChannelState::Ready); 116 | /* OK, next step */ 117 | } 118 | 119 | FALLTHROUGH; case Ready: 120 | { 121 | if(!m_connector.process()) 122 | { 123 | setChannelState(ChannelState::Close); 124 | } 125 | return; 126 | } 127 | 128 | case Close: 129 | { 130 | DEBUGCH << "closeChannel"; 131 | m_connector.close(); 132 | setChannelState(ChannelState::WaitClose); 133 | } 134 | 135 | FALLTHROUGH; case WaitClose: 136 | { 137 | DEBUGCH << "Wait close channel"; 138 | if(m_connector.isClosed()) 139 | { 140 | setChannelState(ChannelState::Freeing); 141 | } 142 | else 143 | { 144 | m_connector.process(); 145 | return; 146 | } 147 | } 148 | 149 | FALLTHROUGH; case Freeing: 150 | { 151 | DEBUGCH << "free Channel"; 152 | 153 | int ret = libssh2_channel_free(m_sshChannel); 154 | if(ret == LIBSSH2_ERROR_EAGAIN) 155 | { 156 | return; 157 | } 158 | if(ret < 0) 159 | { 160 | if(!m_error) 161 | { 162 | m_error = true; 163 | qCWarning(logsshtunneloutconnection) << "Failed to free channel: " << sshErrorToString(ret); 164 | } 165 | } 166 | 167 | if(m_error) 168 | { 169 | setChannelState(ChannelState::Error); 170 | } 171 | else 172 | { 173 | setChannelState(ChannelState::Free); 174 | } 175 | m_sshChannel = nullptr; 176 | QObject::disconnect(m_sshClient, &SshClient::sshDataReceived, this, &SshTunnelOutConnection::sshDataReceived); 177 | return; 178 | } 179 | 180 | case Free: 181 | { 182 | qCDebug(logsshtunneloutconnection) << "Channel" << m_name << "is free"; 183 | return; 184 | } 185 | 186 | case Error: 187 | { 188 | qCDebug(logsshtunneloutconnection) << "Channel" << m_name << "is in error state"; 189 | setChannelState(Free); 190 | return; 191 | } 192 | } 193 | } 194 | 195 | -------------------------------------------------------------------------------- /qtssh/sshscpget.cpp: -------------------------------------------------------------------------------- 1 | #include "sshscpget.h" 2 | #include "sshclient.h" 3 | #include 4 | #include 5 | 6 | #ifndef PAGE_SIZE 7 | #define PAGE_SIZE (4*1024) 8 | #endif 9 | 10 | Q_LOGGING_CATEGORY(logscpget, "ssh.scpget", QtWarningMsg) 11 | 12 | SshScpGet::SshScpGet(const QString &name, SshClient *client): 13 | SshChannel(name, client) 14 | { 15 | } 16 | 17 | SshScpGet::~SshScpGet() 18 | { 19 | qCDebug(logscpget) << "free Channel:" << m_name; 20 | } 21 | 22 | void SshScpGet::close() 23 | { 24 | setChannelState(ChannelState::Close); 25 | sshDataReceived(); 26 | } 27 | 28 | 29 | void SshScpGet::get(const QString &source, const QString &dest) 30 | { 31 | m_source = source; 32 | m_dest = dest; 33 | setChannelState(ChannelState::Openning); 34 | sshDataReceived(); 35 | } 36 | 37 | 38 | void SshScpGet::sshDataReceived() 39 | { 40 | qCDebug(logscpget) << "Channel "<< m_name << "State:" << channelState(); 41 | switch(channelState()) 42 | { 43 | case Openning: 44 | { 45 | if ( ! m_sshClient->takeChannelCreationMutex(this) ) 46 | { 47 | return; 48 | } 49 | m_sshChannel = libssh2_scp_recv2(m_sshClient->session(), qPrintable(m_source), &m_fileinfo); 50 | m_sshClient->releaseChannelCreationMutex(this); 51 | if (m_sshChannel == nullptr) 52 | { 53 | int ret = libssh2_session_last_error(m_sshClient->session(), nullptr, nullptr, 0); 54 | if(ret == LIBSSH2_ERROR_EAGAIN) 55 | { 56 | return; 57 | } 58 | if(!m_error) 59 | { 60 | m_error = true; 61 | emit failed(); 62 | } 63 | setChannelState(ChannelState::Error); 64 | qCWarning(logscpget) << "Channel session open failed"; 65 | return; 66 | } 67 | qCDebug(logscpget) << "Channel session opened"; 68 | setChannelState(ChannelState::Exec); 69 | } 70 | 71 | FALLTHROUGH; case Exec: 72 | { 73 | m_file.setFileName(m_dest); 74 | if(!m_file.open(QIODevice::WriteOnly)) 75 | { 76 | if(!m_error) 77 | { 78 | m_error = true; 79 | emit failed(); 80 | qCWarning(logscpget) << "Can't open destination file"; 81 | } 82 | setChannelState(ChannelState::Close); 83 | sshDataReceived(); 84 | return; 85 | } 86 | 87 | setChannelState(ChannelState::Ready); 88 | /* OK, next step */ 89 | } 90 | 91 | FALLTHROUGH; case Ready: 92 | { 93 | while(m_got < m_fileinfo.st_size) 94 | { 95 | char mem[PAGE_SIZE]; 96 | int amount=sizeof(mem); 97 | 98 | if((m_fileinfo.st_size - m_got) < amount) { 99 | amount = static_cast(m_fileinfo.st_size - m_got); 100 | } 101 | 102 | 103 | ssize_t retsz = libssh2_channel_read_ex(m_sshChannel, 0, mem, static_cast(amount)); 104 | if(retsz == LIBSSH2_ERROR_EAGAIN) 105 | { 106 | return; 107 | } 108 | 109 | if(retsz < 0) 110 | { 111 | if(!m_error) 112 | { 113 | m_error = true; 114 | emit failed(); 115 | qCWarning(logscpget) << "Can't read result (" << sshErrorToString(static_cast(retsz)) << ")"; 116 | } 117 | setChannelState(ChannelState::Close); 118 | sshDataReceived(); 119 | return; 120 | } 121 | 122 | m_file.write(mem, retsz); 123 | m_got += retsz; 124 | emit progress(m_got, m_fileinfo.st_size); 125 | } 126 | setChannelState(ChannelState::Close); 127 | } 128 | 129 | FALLTHROUGH; case Close: 130 | { 131 | m_file.close(); 132 | if(m_got != m_fileinfo.st_size) 133 | { 134 | qCDebug(logscpget) << m_name << "Transfer not completed"; 135 | m_file.remove(); 136 | emit failed(); 137 | } 138 | else 139 | { 140 | emit finished(); 141 | } 142 | 143 | qCDebug(logscpget) << m_name << "closeChannel"; 144 | int ret = libssh2_channel_close(m_sshChannel); 145 | if(ret == LIBSSH2_ERROR_EAGAIN) 146 | { 147 | return; 148 | } 149 | if(ret < 0) 150 | { 151 | if(!m_error) 152 | { 153 | m_error = true; 154 | emit failed(); 155 | qCWarning(logscpget) << "Failed to channel_close: " << sshErrorToString(ret); 156 | } 157 | } 158 | setChannelState(ChannelState::WaitClose); 159 | } 160 | 161 | FALLTHROUGH; case WaitClose: 162 | { 163 | qCDebug(logscpget) << "Wait close channel:" << m_name; 164 | int ret = libssh2_channel_wait_closed(m_sshChannel); 165 | if(ret == LIBSSH2_ERROR_EAGAIN) 166 | { 167 | return; 168 | } 169 | if(ret < 0) 170 | { 171 | if(!m_error) 172 | { 173 | m_error = true; 174 | emit failed(); 175 | qCWarning(logscpget) << "Failed to channel_wait_close: " << sshErrorToString(ret); 176 | } 177 | } 178 | setChannelState(ChannelState::Freeing); 179 | } 180 | 181 | FALLTHROUGH; case Freeing: 182 | { 183 | qCDebug(logscpget) << "free Channel:" << m_name; 184 | 185 | int ret = libssh2_channel_free(m_sshChannel); 186 | if(ret == LIBSSH2_ERROR_EAGAIN) 187 | { 188 | return; 189 | } 190 | if(ret < 0) 191 | { 192 | if(!m_error) 193 | { 194 | m_error = true; 195 | emit failed(); 196 | qCWarning(logscpget) << "Failed to free channel: " << sshErrorToString(ret); 197 | } 198 | } 199 | if(m_error) 200 | { 201 | setChannelState(ChannelState::Error); 202 | } 203 | else 204 | { 205 | setChannelState(ChannelState::Free); 206 | } 207 | m_sshChannel = nullptr; 208 | QObject::disconnect(m_sshClient, &SshClient::sshDataReceived, this, &SshScpGet::sshDataReceived); 209 | return; 210 | } 211 | 212 | case Free: 213 | { 214 | qCDebug(logscpget) << "Channel" << m_name << "is free"; 215 | return; 216 | } 217 | 218 | case Error: 219 | { 220 | emit failed(); 221 | qCDebug(logscpget) << "Channel" << m_name << "is in error state"; 222 | return; 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /qtssh/sshscpsend.cpp: -------------------------------------------------------------------------------- 1 | #include "sshscpsend.h" 2 | #include "sshclient.h" 3 | #include 4 | #include 5 | 6 | 7 | Q_LOGGING_CATEGORY(logscpsend, "ssh.scpsend", QtWarningMsg) 8 | 9 | SshScpSend::SshScpSend(const QString &name, SshClient *client): 10 | SshChannel(name, client) 11 | { 12 | } 13 | 14 | SshScpSend::~SshScpSend() 15 | { 16 | qCDebug(logscpsend) << "free Channel:" << m_name; 17 | } 18 | 19 | void SshScpSend::close() 20 | { 21 | setChannelState(ChannelState::Close); 22 | sshDataReceived(); 23 | } 24 | 25 | 26 | void SshScpSend::send(const QString &source, QString dest) 27 | { 28 | m_source = source; 29 | m_dest = dest; 30 | setChannelState(ChannelState::Openning); 31 | sshDataReceived(); 32 | } 33 | 34 | void SshScpSend::sshDataReceived() 35 | { 36 | qCDebug(logscpsend) << "Channel "<< m_name << "State:" << channelState(); 37 | switch(channelState()) 38 | { 39 | case Openning: 40 | { 41 | stat(m_source.toStdString().c_str(), &m_fileinfo); 42 | if ( ! m_sshClient->takeChannelCreationMutex(this) ) 43 | { 44 | return; 45 | } 46 | m_sshChannel = libssh2_scp_send64(m_sshClient->session(), m_dest.toStdString().c_str(), m_fileinfo.st_mode & 0777, m_fileinfo.st_size, 0, 0); 47 | m_sshClient->releaseChannelCreationMutex(this); 48 | if (m_sshChannel == nullptr) 49 | { 50 | int ret = libssh2_session_last_error(m_sshClient->session(), nullptr, nullptr, 0); 51 | if(ret == LIBSSH2_ERROR_EAGAIN) 52 | { 53 | return; 54 | } 55 | if(!m_error) 56 | { 57 | m_error = true; 58 | emit failed(); 59 | } 60 | setChannelState(ChannelState::Error); 61 | qCWarning(logscpsend) << "Channel session open failed"; 62 | return; 63 | } 64 | qCDebug(logscpsend) << "Channel session opened"; 65 | setChannelState(ChannelState::Exec); 66 | } 67 | 68 | FALLTHROUGH; case Exec: 69 | { 70 | m_file.setFileName(m_source); 71 | if(!m_file.open(QIODevice::ReadOnly)) 72 | { 73 | if(!m_error) 74 | { 75 | m_error = true; 76 | emit failed(); 77 | qCWarning(logscpsend) << "Can't open source file"; 78 | } 79 | setChannelState(ChannelState::Close); 80 | sshDataReceived(); 81 | return; 82 | } 83 | 84 | setChannelState(ChannelState::Ready); 85 | /* OK, next step */ 86 | } 87 | 88 | FALLTHROUGH; case Ready: 89 | { 90 | while(!m_file.atEnd()) 91 | { 92 | if(m_dataInBuf == 0) 93 | { 94 | m_dataInBuf = m_file.read(m_buffer, PAGE_SIZE); 95 | } 96 | 97 | ssize_t retsz = libssh2_channel_write_ex(m_sshChannel, 0, m_buffer + m_offset, static_cast(m_dataInBuf - m_offset)); 98 | if(retsz == LIBSSH2_ERROR_EAGAIN) 99 | { 100 | return; 101 | } 102 | 103 | if(retsz < 0) 104 | { 105 | if(!m_error) 106 | { 107 | m_error = true; 108 | emit failed(); 109 | qCWarning(logscpsend) << "Can't write result (" << sshErrorToString(static_cast(retsz)) << ")"; 110 | } 111 | setChannelState(ChannelState::Close); 112 | sshDataReceived(); 113 | return; 114 | } 115 | 116 | m_sent += retsz; 117 | m_offset += retsz; 118 | if(m_offset == m_dataInBuf) 119 | { 120 | m_dataInBuf = 0; 121 | m_offset = 0; 122 | } 123 | emit progress(m_sent, m_file.size()); 124 | } 125 | setChannelState(ChannelState::Close); 126 | } 127 | 128 | FALLTHROUGH; case Close: 129 | { 130 | m_file.close(); 131 | if(m_sent != m_file.size()) 132 | { 133 | qCDebug(logscpsend) << m_name << "Transfer not completed"; 134 | emit failed(); 135 | } 136 | else 137 | { 138 | emit finished(); 139 | } 140 | 141 | qCDebug(logscpsend) << m_name << "closeChannel"; 142 | int ret = libssh2_channel_close(m_sshChannel); 143 | if(ret == LIBSSH2_ERROR_EAGAIN) 144 | { 145 | return; 146 | } 147 | if(ret < 0) 148 | { 149 | if(!m_error) 150 | { 151 | m_error = true; 152 | emit failed(); 153 | qCWarning(logscpsend) << "Failed to channel_close: " << sshErrorToString(ret); 154 | } 155 | } 156 | setChannelState(ChannelState::WaitClose); 157 | } 158 | 159 | FALLTHROUGH; case WaitClose: 160 | { 161 | qCDebug(logscpsend) << "Wait close channel:" << m_name; 162 | int ret = libssh2_channel_wait_closed(m_sshChannel); 163 | if(ret == LIBSSH2_ERROR_EAGAIN) 164 | { 165 | return; 166 | } 167 | if(ret < 0) 168 | { 169 | if(!m_error) 170 | { 171 | m_error = true; 172 | emit failed(); 173 | qCWarning(logscpsend) << "Failed to channel_wait_close: " << sshErrorToString(ret); 174 | } 175 | } 176 | setChannelState(ChannelState::Freeing); 177 | } 178 | 179 | FALLTHROUGH; case Freeing: 180 | { 181 | qCDebug(logscpsend) << "free Channel:" << m_name; 182 | 183 | int ret = libssh2_channel_free(m_sshChannel); 184 | if(ret == LIBSSH2_ERROR_EAGAIN) 185 | { 186 | return; 187 | } 188 | if(ret < 0) 189 | { 190 | if(!m_error) 191 | { 192 | m_error = true; 193 | emit failed(); 194 | qCWarning(logscpsend) << "Failed to free channel: " << sshErrorToString(ret); 195 | } 196 | } 197 | if(m_error) 198 | { 199 | setChannelState(ChannelState::Error); 200 | } 201 | else 202 | { 203 | setChannelState(ChannelState::Free); 204 | } 205 | m_sshChannel = nullptr; 206 | QObject::disconnect(m_sshClient, &SshClient::sshDataReceived, this, &SshScpSend::sshDataReceived); 207 | return; 208 | } 209 | 210 | case Free: 211 | { 212 | qCDebug(logscpsend) << "Channel" << m_name << "is free"; 213 | return; 214 | } 215 | 216 | case Error: 217 | { 218 | emit failed(); 219 | qCDebug(logscpsend) << "Channel" << m_name << "is in error state"; 220 | return; 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /qtssh/sshtunnelin.cpp: -------------------------------------------------------------------------------- 1 | #include "sshtunnelin.h" 2 | #include "sshclient.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | Q_LOGGING_CATEGORY(logsshtunnelin, "ssh.tunnelin", QtWarningMsg) 11 | 12 | #define BUFFER_LEN (16384) 13 | 14 | 15 | SshTunnelIn::SshTunnelIn(const QString &name, SshClient *client) 16 | : SshChannel(name, client) 17 | { 18 | 19 | } 20 | 21 | SshTunnelIn::~SshTunnelIn() 22 | { 23 | qCDebug(logsshtunnelin) << m_name << "SshTunnelIn Destroyed"; 24 | } 25 | 26 | void SshTunnelIn::listen(QString host, quint16 localPort, quint16 remotePort, QString listenHost, int queueSize) 27 | { 28 | qCDebug(logsshtunnelin) << m_name << "listen(" << remotePort << " -> " << host << ":" << localPort << ")"; 29 | m_localTcpPort = localPort; 30 | m_remoteTcpPort = remotePort; 31 | m_queueSize = queueSize; 32 | m_targethost = host; 33 | m_listenhost = listenHost; 34 | m_retryListen = 10; 35 | setChannelState(ChannelState::Exec); 36 | sshDataReceived(); 37 | } 38 | 39 | quint16 SshTunnelIn::localPort() 40 | { 41 | return static_cast(m_localTcpPort); 42 | } 43 | 44 | quint16 SshTunnelIn::remotePort() 45 | { 46 | if(m_remoteTcpPort == 0) return static_cast(m_boundPort); 47 | return static_cast(m_remoteTcpPort); 48 | } 49 | 50 | void SshTunnelIn::sshDataReceived() 51 | { 52 | switch(channelState()) 53 | { 54 | case Openning: 55 | { 56 | qCDebug(logsshtunnelin) << "Channel session opened"; 57 | break; 58 | } 59 | 60 | case Exec: 61 | { 62 | do 63 | { 64 | if ( ! m_sshClient->takeChannelCreationMutex(this) ) 65 | { 66 | qCWarning(logsshtunnelin) << m_name << "mutex busy"; 67 | return; 68 | } 69 | 70 | m_sshListener = libssh2_channel_forward_listen_ex(m_sshClient->session(), qPrintable(m_listenhost), m_remoteTcpPort, &m_boundPort, m_queueSize); 71 | m_sshClient->releaseChannelCreationMutex(this); 72 | 73 | if(m_sshListener == nullptr) 74 | { 75 | char *emsg; 76 | int size; 77 | int ret = libssh2_session_last_error(m_sshClient->session(), &emsg, &size, 0); 78 | if(ret == LIBSSH2_ERROR_EAGAIN) 79 | { 80 | return; 81 | } 82 | if ( ret==LIBSSH2_ERROR_REQUEST_DENIED && m_retryListen > 0 ) 83 | { 84 | m_retryListen--; 85 | return; 86 | } 87 | else 88 | { 89 | if(m_remoteTcpPort == 0) 90 | { 91 | m_remoteTcpPort = m_localTcpPort; 92 | m_retryListen = 5; 93 | qCWarning(logsshtunnelin) << "The server refuse dynamic port, try with the same port as local"; 94 | return; 95 | } 96 | } 97 | 98 | setChannelState(ChannelState::Error); 99 | qCWarning(logsshtunnelin) << "Channel session open failed: " << emsg; 100 | return; 101 | } 102 | qCDebug(logsshtunnelin) << m_name << "Create Reverse tunnel for " << m_listenhost << m_remoteTcpPort << m_boundPort << m_queueSize; 103 | } while (m_sshListener == nullptr); 104 | /* OK, next step */ 105 | setChannelState(ChannelState::Ready); 106 | } 107 | 108 | FALLTHROUGH; case Ready: 109 | { 110 | LIBSSH2_CHANNEL *newChannel; 111 | if ( ! m_sshClient->takeChannelCreationMutex(this) ) 112 | { 113 | return; 114 | } 115 | newChannel = libssh2_channel_forward_accept(m_sshListener); 116 | m_sshClient->releaseChannelCreationMutex(this); 117 | 118 | if(newChannel == nullptr) 119 | { 120 | char *emsg; 121 | int size; 122 | int ret = libssh2_session_last_error(m_sshClient->session(), &emsg, &size, 0); 123 | if(ret == LIBSSH2_ERROR_EAGAIN) 124 | { 125 | return; 126 | } 127 | 128 | qCWarning(logsshtunnelin) << "Channel session open failed: " << emsg; 129 | return; 130 | } 131 | 132 | /* We have a new connection on the remote port, need to create a connection tunnel */ 133 | qCDebug(logsshtunnelin) << "SshTunnelIn new connection"; 134 | SshTunnelInConnection *connection = m_sshClient->getChannel(m_name + QString("_%1").arg(m_connectionCounter++)); 135 | connection->configure(newChannel, m_localTcpPort, m_targethost); 136 | m_connection.append(connection); 137 | QObject::connect(connection, &SshTunnelInConnection::stateChanged, this, &SshTunnelIn::connectionStateChanged); 138 | emit connectionChanged(m_connection.size()); 139 | break; 140 | } 141 | 142 | case Close: 143 | { 144 | if(m_sshListener) 145 | { 146 | if(libssh2_channel_forward_cancel(m_sshListener) == LIBSSH2_ERROR_EAGAIN) 147 | { 148 | return; 149 | } 150 | m_sshListener = nullptr; 151 | } 152 | setChannelState(ChannelState::WaitClose); 153 | FALLTHROUGH; 154 | } 155 | 156 | case WaitClose: 157 | { 158 | qCDebug(logsshtunnelin) << "Wait close channel:" << m_name << " (connections:"<< m_connection.count() << ")"; 159 | if(m_connection.count() != 0) 160 | { 161 | return; 162 | } 163 | setChannelState(ChannelState::Freeing); 164 | FALLTHROUGH; 165 | } 166 | 167 | case Freeing: 168 | { 169 | qCDebug(logsshtunnelin) << "free Channel"; 170 | setChannelState(ChannelState::Free); 171 | } 172 | 173 | FALLTHROUGH; case Free: 174 | { 175 | qCDebug(logsshtunnelin) << "Channel" << m_name << "is free"; 176 | return; 177 | } 178 | 179 | case Error: 180 | { 181 | qCDebug(logsshtunnelin) << "Channel" << m_name << "is in error state"; 182 | return; 183 | } 184 | } 185 | } 186 | 187 | void SshTunnelIn::connectionStateChanged() 188 | { 189 | QObject *obj = QObject::sender(); 190 | SshTunnelInConnection *connection = qobject_cast(obj); 191 | if(connection) 192 | { 193 | if(connection->channelState() == SshChannel::ChannelState::Free) 194 | { 195 | m_connection.removeAll(connection); 196 | emit connectionChanged(m_connection.count()); 197 | 198 | if(m_connection.count() == 0 && channelState() == SshChannel::ChannelState::WaitClose) 199 | { 200 | setChannelState(SshChannel::ChannelState::Freeing); 201 | } 202 | } 203 | } 204 | } 205 | 206 | void SshTunnelIn::flushTx() const 207 | { 208 | for(auto &c: m_connection) 209 | { 210 | c->flushTx(); 211 | } 212 | } 213 | 214 | void SshTunnelIn::close() 215 | { 216 | setChannelState(ChannelState::Close); 217 | sshDataReceived(); 218 | } 219 | -------------------------------------------------------------------------------- /qtssh/sshclient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "sshchannel.h" 9 | #include "sshkey.h" 10 | #include 11 | 12 | #ifndef FALLTHROUGH 13 | #if __has_cpp_attribute(fallthrough) 14 | #define FALLTHROUGH [[fallthrough]] 15 | #elif __has_cpp_attribute(clang::fallthrough) 16 | #define FALLTHROUGH [[clang::fallthrough]] 17 | #else 18 | #define FALLTHROUGH 19 | #endif 20 | #endif 21 | 22 | Q_DECLARE_LOGGING_CATEGORY(sshclient) 23 | class SshScpGet; 24 | class SshScpSend; 25 | class SshSFtp; 26 | class SshTunnelIn; 27 | class SshTunnelOut; 28 | class QNetworkProxy; 29 | 30 | class SshClient : public QObject { 31 | Q_OBJECT 32 | 33 | public: 34 | enum SshState { 35 | Unconnected, 36 | SocketConnection, 37 | WaitingSocketConnection, 38 | Initialize, 39 | HandShake, 40 | GetAuthenticationMethodes, 41 | Authentication, 42 | Ready, 43 | DisconnectingChannel, 44 | DisconnectingSession, 45 | FreeSession, 46 | Error, 47 | }; 48 | Q_ENUM(SshState) 49 | 50 | private: 51 | static int s_nbInstance; 52 | LIBSSH2_SESSION * m_session {nullptr}; 53 | LIBSSH2_KNOWNHOSTS * m_knownHosts {nullptr}; 54 | QList m_channels; 55 | 56 | QString m_name; 57 | QTcpSocket m_socket; 58 | QNetworkProxy *m_proxy {nullptr}; 59 | qint64 m_lastProofOfLive {0}; 60 | 61 | quint16 m_port {0}; 62 | int m_errorcode {0}; 63 | bool m_sshConnected {false}; 64 | int m_connTimeoutCnt; 65 | 66 | QString m_hostname; 67 | QString m_username; 68 | QString m_passphrase; 69 | QString m_privateKey; 70 | QString m_publicKey; 71 | QString m_errorMessage; 72 | QString m_knowhostFiles; 73 | SshKey m_hostKey; 74 | QTimer m_keepalive; 75 | QTimer m_connectionTimeout; 76 | QMutex channelCreationInProgress; 77 | void *currentLockerForChannelCreation {nullptr}; 78 | 79 | public: 80 | SshClient(const QString &name = "noname", QObject * parent = nullptr); 81 | virtual ~SshClient(); 82 | 83 | QString getName() const; 84 | bool takeChannelCreationMutex(void *identifier); 85 | void releaseChannelCreationMutex(void *identifier); 86 | 87 | 88 | 89 | public slots: 90 | int connectToHost(const QString &username, const QString &hostname, quint16 port = 22, QByteArrayList methodes = QByteArrayList(), int connTimeoutMsec = 60000); 91 | bool waitForState(SshClient::SshState state); 92 | void disconnectFromHost(); 93 | void resetError(); 94 | 95 | public: 96 | template 97 | T *getChannel(const QString &name) 98 | { 99 | for(SshChannel *ch: m_channels) 100 | { 101 | if(ch->name() == name) 102 | { 103 | T *proc = qobject_cast(ch); 104 | if(proc) 105 | { 106 | return proc; 107 | } 108 | } 109 | } 110 | 111 | T *res = new T(name, this); 112 | m_channels.append(res); 113 | QObject::connect(res, &SshChannel::stateChanged, this, &SshClient::_channel_free); 114 | emit channelsChanged(m_channels.count()); 115 | return res; 116 | } 117 | 118 | void setKeys(const QString &publicKey, const QString &privateKey); 119 | void setPassphrase(const QString & pass); 120 | bool saveKnownHosts(const QString &file); 121 | void setKownHostFile(const QString &file); 122 | bool addKnownHost (const QString &hostname, const SshKey &key); 123 | QString banner(); 124 | 125 | 126 | LIBSSH2_SESSION *session(); 127 | 128 | 129 | 130 | private slots: 131 | void _sendKeepAlive(); 132 | 133 | 134 | public: /* New function implementation with state machine */ 135 | SshState sshState() const; 136 | 137 | void setName(const QString &name); 138 | 139 | void setProxy(QNetworkProxy *proxy); 140 | 141 | void setConnectTimeout(int timeoutMsec); 142 | 143 | private: /* New function implementation with state machine */ 144 | SshState m_sshState {SshState::Unconnected}; 145 | QByteArrayList m_authenticationMethodes; 146 | void setSshState(const SshState &sshState); 147 | 148 | 149 | private slots: /* New function implementation with state machine */ 150 | void _connection_socketTimeout(); 151 | void _connection_socketError(); 152 | void _connection_socketConnected(); 153 | void _connection_socketDisconnected(); 154 | void _ssh_processEvent(); 155 | void _channel_free(); 156 | 157 | signals: 158 | void sshStateChanged(SshState sshState); 159 | void sshReady(); 160 | void sshDisconnected(); 161 | void sshError(); 162 | 163 | void sshDataReceived(); 164 | void sshEvent(); 165 | void channelsChanged(int); 166 | }; 167 | 168 | inline const char* sshErrorToString(int err) 169 | { 170 | switch(err) 171 | { 172 | case LIBSSH2_ERROR_SOCKET_NONE: 173 | return "LIBSSH2_ERROR_SOCKET_NONE"; 174 | case LIBSSH2_ERROR_BANNER_RECV: 175 | return "LIBSSH2_ERROR_BANNER_RECV"; 176 | case LIBSSH2_ERROR_BANNER_SEND: 177 | return "LIBSSH2_ERROR_BANNER_SEND"; 178 | case LIBSSH2_ERROR_INVALID_MAC: 179 | return "LIBSSH2_ERROR_INVALID_MAC"; 180 | case LIBSSH2_ERROR_KEX_FAILURE: 181 | return "LIBSSH2_ERROR_KEX_FAILURE"; 182 | case LIBSSH2_ERROR_ALLOC: 183 | return "LIBSSH2_ERROR_ALLOC"; 184 | case LIBSSH2_ERROR_SOCKET_SEND: 185 | return "LIBSSH2_ERROR_SOCKET_SEND"; 186 | case LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE: 187 | return "LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE"; 188 | case LIBSSH2_ERROR_TIMEOUT: 189 | return "LIBSSH2_ERROR_TIMEOUT"; 190 | case LIBSSH2_ERROR_HOSTKEY_INIT: 191 | return "LIBSSH2_ERROR_HOSTKEY_INIT"; 192 | case LIBSSH2_ERROR_HOSTKEY_SIGN: 193 | return "LIBSSH2_ERROR_HOSTKEY_SIGN"; 194 | case LIBSSH2_ERROR_DECRYPT: 195 | return "LIBSSH2_ERROR_DECRYPT"; 196 | case LIBSSH2_ERROR_SOCKET_DISCONNECT: 197 | return "LIBSSH2_ERROR_SOCKET_DISCONNECT"; 198 | case LIBSSH2_ERROR_PROTO: 199 | return "LIBSSH2_ERROR_PROTO"; 200 | case LIBSSH2_ERROR_PASSWORD_EXPIRED: 201 | return "LIBSSH2_ERROR_PASSWORD_EXPIRED"; 202 | case LIBSSH2_ERROR_FILE: 203 | return "LIBSSH2_ERROR_FILE"; 204 | case LIBSSH2_ERROR_METHOD_NONE: 205 | return "LIBSSH2_ERROR_METHOD_NONE"; 206 | case LIBSSH2_ERROR_AUTHENTICATION_FAILED: 207 | return "LIBSSH2_ERROR_AUTHENTICATION_FAILED"; 208 | case LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED: 209 | return "LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED"; 210 | case LIBSSH2_ERROR_CHANNEL_OUTOFORDER: 211 | return "LIBSSH2_ERROR_CHANNEL_OUTOFORDER"; 212 | case LIBSSH2_ERROR_CHANNEL_FAILURE: 213 | return "LIBSSH2_ERROR_CHANNEL_FAILURE"; 214 | case LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED: 215 | return "LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED"; 216 | case LIBSSH2_ERROR_CHANNEL_UNKNOWN: 217 | return "LIBSSH2_ERROR_CHANNEL_UNKNOWN"; 218 | case LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED: 219 | return "LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED"; 220 | case LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED: 221 | return "LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED"; 222 | case LIBSSH2_ERROR_CHANNEL_CLOSED: 223 | return "LIBSSH2_ERROR_CHANNEL_CLOSED"; 224 | case LIBSSH2_ERROR_CHANNEL_EOF_SENT: 225 | return "LIBSSH2_ERROR_CHANNEL_EOF_SENT"; 226 | case LIBSSH2_ERROR_SCP_PROTOCOL: 227 | return "LIBSSH2_ERROR_SCP_PROTOCOL"; 228 | case LIBSSH2_ERROR_ZLIB: 229 | return "LIBSSH2_ERROR_ZLIB"; 230 | case LIBSSH2_ERROR_SOCKET_TIMEOUT: 231 | return "LIBSSH2_ERROR_SOCKET_TIMEOUT"; 232 | case LIBSSH2_ERROR_SFTP_PROTOCOL: 233 | return "LIBSSH2_ERROR_SFTP_PROTOCOL"; 234 | case LIBSSH2_ERROR_REQUEST_DENIED: 235 | return "LIBSSH2_ERROR_REQUEST_DENIED"; 236 | case LIBSSH2_ERROR_METHOD_NOT_SUPPORTED: 237 | return "LIBSSH2_ERROR_METHOD_NOT_SUPPORTED"; 238 | case LIBSSH2_ERROR_INVAL: 239 | return "LIBSSH2_ERROR_INVAL"; 240 | case LIBSSH2_ERROR_INVALID_POLL_TYPE: 241 | return "LIBSSH2_ERROR_INVALID_POLL_TYPE"; 242 | case LIBSSH2_ERROR_PUBLICKEY_PROTOCOL: 243 | return "LIBSSH2_ERROR_PUBLICKEY_PROTOCOL"; 244 | case LIBSSH2_ERROR_EAGAIN: 245 | return "LIBSSH2_ERROR_EAGAIN"; 246 | case LIBSSH2_ERROR_BUFFER_TOO_SMALL: 247 | return "LIBSSH2_ERROR_BUFFER_TOO_SMALL"; 248 | case LIBSSH2_ERROR_BAD_USE: 249 | return "LIBSSH2_ERROR_BAD_USE"; 250 | case LIBSSH2_ERROR_COMPRESS: 251 | return "LIBSSH2_ERROR_COMPRESS"; 252 | case LIBSSH2_ERROR_OUT_OF_BOUNDARY: 253 | return "LIBSSH2_ERROR_OUT_OF_BOUNDARY"; 254 | case LIBSSH2_ERROR_AGENT_PROTOCOL: 255 | return "LIBSSH2_ERROR_AGENT_PROTOCOL"; 256 | case LIBSSH2_ERROR_SOCKET_RECV: 257 | return "LIBSSH2_ERROR_SOCKET_RECV"; 258 | case LIBSSH2_ERROR_ENCRYPT: 259 | return "LIBSSH2_ERROR_ENCRYPT"; 260 | case LIBSSH2_ERROR_BAD_SOCKET: 261 | return "LIBSSH2_ERROR_BAD_SOCKET"; 262 | case LIBSSH2_ERROR_KNOWN_HOSTS: 263 | return "LIBSSH2_ERROR_KNOWN_HOSTS"; 264 | default: 265 | return "Unknown LIBSSH2 ERROR"; 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /qtssh/sshprocess.cpp: -------------------------------------------------------------------------------- 1 | #include "sshprocess.h" 2 | #include "sshclient.h" 3 | #include 4 | #include 5 | 6 | Q_LOGGING_CATEGORY(logsshprocess, "ssh.process", QtWarningMsg) 7 | 8 | SshProcess::SshProcess(const QString &name, SshClient *client) 9 | : SshChannel(name, client) 10 | { 11 | } 12 | 13 | SshProcess::~SshProcess() 14 | { 15 | qCDebug(sshchannel) << "free Channel:" << m_name; 16 | } 17 | 18 | void SshProcess::close() 19 | { 20 | setChannelState(ChannelState::Close); 21 | sshDataReceived(); 22 | } 23 | 24 | QByteArray SshProcess::result() 25 | { 26 | return m_result; 27 | } 28 | 29 | QStringList SshProcess::errMsg() 30 | { 31 | return m_errMsg; 32 | } 33 | 34 | bool SshProcess::isError() 35 | { 36 | return m_error; 37 | } 38 | 39 | void SshProcess::runCommand(const QString &cmd) 40 | { 41 | m_cmd = cmd; 42 | sshDataReceived(); 43 | } 44 | 45 | void SshProcess::sshDataReceived() 46 | { 47 | qCDebug(logsshprocess) << "Channel "<< m_name << "State:" << channelState(); 48 | switch(channelState()) 49 | { 50 | case Openning: 51 | { 52 | if ( ! m_sshClient->takeChannelCreationMutex(this) ) 53 | { 54 | return; 55 | } 56 | m_sshChannel = libssh2_channel_open_ex(m_sshClient->session(), "session", sizeof("session") - 1, LIBSSH2_CHANNEL_WINDOW_DEFAULT, LIBSSH2_CHANNEL_PACKET_DEFAULT, nullptr, 0); 57 | m_sshClient->releaseChannelCreationMutex(this); 58 | if (m_sshChannel == nullptr) 59 | { 60 | int ret = libssh2_session_last_error(m_sshClient->session(), nullptr, nullptr, 0); 61 | if(ret == LIBSSH2_ERROR_EAGAIN) 62 | { 63 | return; 64 | } 65 | if(!m_error) 66 | { 67 | m_error = true; 68 | m_errMsg << QString("Channel session open failed: %1").arg(ret); 69 | emit failed(); 70 | } 71 | setChannelState(ChannelState::Error); 72 | qCWarning(logsshprocess) << "Channel session open failed"; 73 | return; 74 | } 75 | qCDebug(logsshprocess) << "Channel session opened"; 76 | setChannelState(ChannelState::Exec); 77 | } 78 | 79 | FALLTHROUGH; case Exec: 80 | { 81 | if(m_cmd.size() == 0) 82 | { 83 | /* Nothing to process */ 84 | return; 85 | } 86 | qCDebug(logsshprocess) << "runCommand(" << m_cmd << ")"; 87 | int ret = libssh2_channel_process_startup(m_sshChannel, "exec", sizeof("exec") - 1, m_cmd.toStdString().c_str(), static_cast(m_cmd.size())); 88 | if (ret == LIBSSH2_ERROR_EAGAIN) 89 | { 90 | return; 91 | } 92 | if(ret != 0) 93 | { 94 | if(!m_error) 95 | { 96 | m_error = true; 97 | m_errMsg << QString("Failed to run command: %1").arg(ret); 98 | emit failed(); 99 | qCWarning(logsshprocess) << "Failed to run command" << ret; 100 | } 101 | setChannelState(ChannelState::Close); 102 | sshDataReceived(); 103 | return; 104 | } 105 | setChannelState(ChannelState::Ready); 106 | /* OK, next step */ 107 | } 108 | 109 | FALLTHROUGH; case Ready: 110 | { 111 | ssize_t retsz; 112 | char buffer[16*1024]; 113 | 114 | retsz = libssh2_channel_read_ex(m_sshChannel, 0, buffer, 16 * 1024); 115 | if(retsz == LIBSSH2_ERROR_EAGAIN) 116 | { 117 | return; 118 | } 119 | 120 | if(retsz < 0) 121 | { 122 | if(!m_error) 123 | { 124 | m_error = true; 125 | m_errMsg << QString("Can't read result (%1)").arg(sshErrorToString(static_cast(retsz))); 126 | emit failed(); 127 | qCWarning(logsshprocess) << "Can't read result (" << sshErrorToString(static_cast(retsz)) << ")"; 128 | } 129 | setChannelState(ChannelState::Close); 130 | sshDataReceived(); 131 | return; 132 | } 133 | 134 | m_result.append(buffer, static_cast(retsz)); 135 | 136 | retsz = libssh2_channel_read_stderr(m_sshChannel, buffer, 16 * 1024); 137 | if(retsz == LIBSSH2_ERROR_EAGAIN) 138 | { 139 | return; 140 | } 141 | if (retsz < 0) 142 | { 143 | if(!m_error) 144 | { 145 | m_error = true; 146 | m_errMsg << QString("Can't read stderr msg (%1)").arg(sshErrorToString(static_cast(retsz))); 147 | emit failed(); 148 | qCWarning(logsshprocess) << "Can't read stderr msg (" << sshErrorToString(static_cast(retsz)) << ")"; 149 | } 150 | setChannelState(ChannelState::Close); 151 | sshDataReceived(); 152 | return; 153 | } 154 | else if (retsz > 0) 155 | { 156 | if (!m_error) 157 | { 158 | m_error = true; 159 | emit failed(); 160 | } 161 | qCWarning(logsshprocess) << "Run command error"; 162 | m_errMsg << QString("Run command error: (%1)").arg(buffer); 163 | } 164 | 165 | if (libssh2_channel_eof(m_sshChannel) == 1) 166 | { 167 | qCDebug(logsshprocess) << "runCommand(" << m_cmd << ") RESULT: " << m_result; 168 | setChannelState(ChannelState::Close); 169 | emit finished(); 170 | } 171 | } 172 | 173 | FALLTHROUGH; case Close: 174 | { 175 | qCDebug(logsshprocess) << "closeChannel:" << m_name; 176 | int ret = libssh2_channel_close(m_sshChannel); 177 | if(ret == LIBSSH2_ERROR_EAGAIN) 178 | { 179 | return; 180 | } 181 | if(ret < 0) 182 | { 183 | if(!m_error) 184 | { 185 | m_error = true; 186 | m_errMsg << QString("Failed to channel_close: %1").arg(sshErrorToString(ret)); 187 | emit failed(); 188 | qCWarning(logsshprocess) << "Failed to channel_close: " << sshErrorToString(ret); 189 | } 190 | } 191 | setChannelState(ChannelState::WaitClose); 192 | } 193 | 194 | FALLTHROUGH; case WaitClose: 195 | { 196 | qCDebug(logsshprocess) << "Wait close channel:" << m_name; 197 | int ret = libssh2_channel_wait_closed(m_sshChannel); 198 | if(ret == LIBSSH2_ERROR_EAGAIN) 199 | { 200 | return; 201 | } 202 | if(ret < 0) 203 | { 204 | if(!m_error) 205 | { 206 | m_error = true; 207 | m_errMsg << QString("Failed to channel_wait_close: %1").arg(sshErrorToString(ret)); 208 | emit failed(); 209 | qCWarning(logsshprocess) << "Failed to channel_wait_close: " << sshErrorToString(ret); 210 | } 211 | } 212 | setChannelState(ChannelState::Freeing); 213 | } 214 | 215 | FALLTHROUGH; case Freeing: 216 | { 217 | qCDebug(logsshprocess) << "free Channel:" << m_name; 218 | 219 | int ret = libssh2_channel_free(m_sshChannel); 220 | if(ret == LIBSSH2_ERROR_EAGAIN) 221 | { 222 | return; 223 | } 224 | if(ret < 0) 225 | { 226 | if(!m_error) 227 | { 228 | m_error = true; 229 | m_errMsg << QString("Failed to free channel: %1").arg(sshErrorToString(ret)); 230 | emit failed(); 231 | qCWarning(logsshprocess) << "Failed to free channel: " << sshErrorToString(ret); 232 | } 233 | } 234 | if(m_error) 235 | { 236 | setChannelState(ChannelState::Error); 237 | } 238 | else 239 | { 240 | setChannelState(ChannelState::Free); 241 | } 242 | m_sshChannel = nullptr; 243 | QObject::disconnect(m_sshClient, &SshClient::sshDataReceived, this, &SshProcess::sshDataReceived); 244 | return; 245 | } 246 | 247 | case Free: 248 | { 249 | qCDebug(logsshprocess) << "Channel" << m_name << "is free"; 250 | return; 251 | } 252 | 253 | case Error: 254 | { 255 | emit failed(); 256 | qCDebug(logsshprocess) << "Channel" << m_name << "is in error state"; 257 | setChannelState(ChannelState::Free); 258 | sshDataReceived(); 259 | return; 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /doc/sshclient.md: -------------------------------------------------------------------------------- 1 | # SshClient API Documentation 2 | 3 | ## Overview 4 | 5 | `SshClient` is the main class for managing SSH connections in QtSsh. It provides the foundation for all SSH operations including authentication, connection management, and channel creation. 6 | 7 | ## Class Hierarchy 8 | 9 | ``` 10 | QObject 11 | └── SshClient 12 | ``` 13 | 14 | ## Header 15 | 16 | ```cpp 17 | #include "sshclient.h" 18 | ``` 19 | 20 | ## Constructor 21 | 22 | ```cpp 23 | SshClient(const QString &name = "noname", QObject *parent = nullptr) 24 | ``` 25 | 26 | Creates a new SSH client instance. 27 | 28 | **Parameters:** 29 | - `name`: Optional identifier for this client instance (useful for debugging) 30 | - `parent`: Optional parent QObject 31 | 32 | ## Connection Management 33 | 34 | ### connectToHost 35 | 36 | ```cpp 37 | int connectToHost(const QString &username, 38 | const QString &hostname, 39 | quint16 port = 22, 40 | QByteArrayList methodes = QByteArrayList(), 41 | int connTimeoutMsec = 60000) 42 | ``` 43 | 44 | Initiates a connection to an SSH server. 45 | 46 | **Parameters:** 47 | - `username`: Username for authentication 48 | - `hostname`: Server hostname or IP address 49 | - `port`: SSH port (default: 22) 50 | - `methodes`: List of preferred authentication methods 51 | - `connTimeoutMsec`: Connection timeout in milliseconds (default: 60000) 52 | 53 | **Returns:** Status code (0 on success) 54 | 55 | **Example:** 56 | ```cpp 57 | SshClient *client = new SshClient("myConnection"); 58 | client->connectToHost("user", "example.com", 22); 59 | ``` 60 | 61 | ### disconnectFromHost 62 | 63 | ```cpp 64 | void disconnectFromHost() 65 | ``` 66 | 67 | Disconnects from the SSH server and closes all active channels. 68 | 69 | ### waitForState 70 | 71 | ```cpp 72 | bool waitForState(SshClient::SshState state) 73 | ``` 74 | 75 | Blocks until the client reaches the specified state. 76 | 77 | **Parameters:** 78 | - `state`: Target SSH state to wait for 79 | 80 | **Returns:** `true` if state was reached, `false` on timeout 81 | 82 | **Example:** 83 | ```cpp 84 | client->connectToHost("user", "example.com"); 85 | if (client->waitForState(SshClient::Ready)) { 86 | // Connection established 87 | } 88 | ``` 89 | 90 | ## Authentication 91 | 92 | ### setKeys 93 | 94 | ```cpp 95 | void setKeys(const QString &publicKey, const QString &privateKey) 96 | ``` 97 | 98 | Sets SSH key pair for public key authentication. 99 | 100 | **Parameters:** 101 | - `publicKey`: Path to public key file 102 | - `privateKey`: Path to private key file 103 | 104 | **Example:** 105 | ```cpp 106 | client->setKeys("/home/user/.ssh/id_rsa.pub", "/home/user/.ssh/id_rsa"); 107 | ``` 108 | 109 | ### setPassphrase 110 | 111 | ```cpp 112 | void setPassphrase(const QString &pass) 113 | ``` 114 | 115 | Sets the passphrase for password authentication or encrypted private keys. 116 | 117 | **Parameters:** 118 | - `pass`: Password or passphrase 119 | 120 | ## Known Hosts Management 121 | 122 | ### setKownHostFile 123 | 124 | ```cpp 125 | void setKownHostFile(const QString &file) 126 | ``` 127 | 128 | Sets the path to the known_hosts file for host verification. 129 | 130 | **Parameters:** 131 | - `file`: Path to known_hosts file 132 | 133 | ### addKnownHost 134 | 135 | ```cpp 136 | bool addKnownHost(const QString &hostname, const SshKey &key) 137 | ``` 138 | 139 | Adds a host key to the known hosts list. 140 | 141 | **Parameters:** 142 | - `hostname`: Host to add 143 | - `key`: Host's public key 144 | 145 | **Returns:** `true` on success 146 | 147 | ### saveKnownHosts 148 | 149 | ```cpp 150 | bool saveKnownHosts(const QString &file) 151 | ``` 152 | 153 | Saves the known hosts list to a file. 154 | 155 | **Parameters:** 156 | - `file`: Output file path 157 | 158 | **Returns:** `true` on success 159 | 160 | ## Channel Management 161 | 162 | ### getChannel 163 | 164 | ```cpp 165 | template 166 | T *getChannel(const QString &name) 167 | ``` 168 | 169 | Gets or creates a channel of the specified type. 170 | 171 | **Template Parameters:** 172 | - `T`: Channel type (SshProcess, SshSFtp, SshScpGet, SshScpSend, SshTunnelIn, SshTunnelOut) 173 | 174 | **Parameters:** 175 | - `name`: Unique name for the channel 176 | 177 | **Returns:** Pointer to the channel 178 | 179 | **Example:** 180 | ```cpp 181 | // Get or create a process channel 182 | SshProcess *proc = client->getChannel("myProcess"); 183 | 184 | // Get or create an SFTP channel 185 | SshSFtp *sftp = client->getChannel("myFtp"); 186 | ``` 187 | 188 | ## Properties 189 | 190 | ### getName 191 | 192 | ```cpp 193 | QString getName() const 194 | ``` 195 | 196 | Returns the name of this SSH client instance. 197 | 198 | ### sshState 199 | 200 | ```cpp 201 | SshState sshState() const 202 | ``` 203 | 204 | Returns the current SSH connection state. 205 | 206 | **States:** 207 | - `Unconnected`: Not connected 208 | - `SocketConnection`: Establishing TCP connection 209 | - `WaitingSocketConnection`: Waiting for TCP connection 210 | - `Initialize`: Initializing SSH session 211 | - `HandShake`: Performing SSH handshake 212 | - `GetAuthenticationMethodes`: Retrieving authentication methods 213 | - `Authentication`: Authenticating user 214 | - `Ready`: Connected and ready 215 | - `DisconnectingChannel`: Closing channels 216 | - `DisconnectingSession`: Closing SSH session 217 | - `FreeSession`: Freeing resources 218 | - `Error`: Error state 219 | 220 | ### setName 221 | 222 | ```cpp 223 | void setName(const QString &name) 224 | ``` 225 | 226 | Sets the name for this client instance. 227 | 228 | ### session 229 | 230 | ```cpp 231 | LIBSSH2_SESSION *session() 232 | ``` 233 | 234 | Returns the underlying libssh2 session pointer (for advanced use). 235 | 236 | ## Configuration 237 | 238 | ### setProxy 239 | 240 | ```cpp 241 | void setProxy(QNetworkProxy *proxy) 242 | ``` 243 | 244 | Sets a network proxy for the connection. 245 | 246 | **Parameters:** 247 | - `proxy`: Pointer to QNetworkProxy object 248 | 249 | ### setConnectTimeout 250 | 251 | ```cpp 252 | void setConnectTimeout(int timeoutMsec) 253 | ``` 254 | 255 | Sets the connection timeout. 256 | 257 | **Parameters:** 258 | - `timeoutMsec`: Timeout in milliseconds 259 | 260 | ## Signals 261 | 262 | ### sshStateChanged 263 | 264 | ```cpp 265 | void sshStateChanged(SshState sshState) 266 | ``` 267 | 268 | Emitted when the SSH connection state changes. 269 | 270 | ### sshReady 271 | 272 | ```cpp 273 | void sshReady() 274 | ``` 275 | 276 | Emitted when the SSH connection is established and ready for use. 277 | 278 | ### sshDisconnected 279 | 280 | ```cpp 281 | void sshDisconnected() 282 | ``` 283 | 284 | Emitted when disconnected from the SSH server. 285 | 286 | ### sshError 287 | 288 | ```cpp 289 | void sshError() 290 | ``` 291 | 292 | Emitted when an SSH error occurs. 293 | 294 | ### sshDataReceived 295 | 296 | ```cpp 297 | void sshDataReceived() 298 | ``` 299 | 300 | Emitted when data is received from the SSH server. 301 | 302 | ### channelsChanged 303 | 304 | ```cpp 305 | void channelsChanged(int count) 306 | ``` 307 | 308 | Emitted when the number of active channels changes. 309 | 310 | ## Error Handling 311 | 312 | ### resetError 313 | 314 | ```cpp 315 | void resetError() 316 | ``` 317 | 318 | Clears the current error state. 319 | 320 | ### Error Codes 321 | 322 | The `sshErrorToString()` helper function converts libssh2 error codes to human-readable strings: 323 | 324 | ```cpp 325 | const char* sshErrorToString(int err) 326 | ``` 327 | 328 | **Example:** 329 | ```cpp 330 | connect(client, &SshClient::sshError, [=]() { 331 | qDebug() << "SSH Error occurred"; 332 | }); 333 | ``` 334 | 335 | ## Complete Example 336 | 337 | ```cpp 338 | #include "sshclient.h" 339 | #include "sshprocess.h" 340 | 341 | int main(int argc, char *argv[]) { 342 | QCoreApplication app(argc, argv); 343 | 344 | SshClient *client = new SshClient("example"); 345 | 346 | // Set authentication 347 | client->setPassphrase("mypassword"); 348 | // or use keys: 349 | // client->setKeys("~/.ssh/id_rsa.pub", "~/.ssh/id_rsa"); 350 | 351 | // Connect to signals 352 | QObject::connect(client, &SshClient::sshReady, [=]() { 353 | qDebug() << "Connected!"; 354 | 355 | // Execute command 356 | SshProcess *proc = client->getChannel("ls"); 357 | proc->runCommand("ls -la"); 358 | 359 | QObject::connect(proc, &SshProcess::finished, [=]() { 360 | qDebug() << proc->result(); 361 | client->disconnectFromHost(); 362 | }); 363 | }); 364 | 365 | QObject::connect(client, &SshClient::sshError, [=]() { 366 | qDebug() << "Connection error"; 367 | app.quit(); 368 | }); 369 | 370 | QObject::connect(client, &SshClient::sshDisconnected, [=]() { 371 | qDebug() << "Disconnected"; 372 | app.quit(); 373 | }); 374 | 375 | // Connect 376 | client->connectToHost("username", "hostname.com"); 377 | 378 | return app.exec(); 379 | } 380 | ``` 381 | 382 | ## Thread Safety 383 | 384 | `SshClient` uses internal mutexes for channel creation but is primarily designed for single-threaded use with Qt's event loop. 385 | 386 | ## See Also 387 | 388 | - [SshChannel](sshchannel.md) - Base channel class 389 | - [SshProcess](sshprocess.md) - Remote command execution 390 | - [SshSFtp](sshsftp.md) - SFTP file operations 391 | - [Quick Start Guide](quickstart.md) 392 | -------------------------------------------------------------------------------- /doc/sshprocess.md: -------------------------------------------------------------------------------- 1 | # SshProcess API Documentation 2 | 3 | ## Overview 4 | 5 | `SshProcess` provides remote command execution over SSH. It allows you to run commands on a remote server and retrieve their output, similar to executing commands locally with `QProcess`. 6 | 7 | ## Class Hierarchy 8 | 9 | ``` 10 | QObject 11 | └── SshChannel 12 | └── SshProcess 13 | ``` 14 | 15 | ## Header 16 | 17 | ```cpp 18 | #include "sshprocess.h" 19 | ``` 20 | 21 | ## Creation 22 | 23 | Channels are created through `SshClient::getChannel()`: 24 | 25 | ```cpp 26 | SshProcess *proc = client->getChannel("processName"); 27 | ``` 28 | 29 | ## Public Methods 30 | 31 | ### runCommand 32 | 33 | ```cpp 34 | void runCommand(const QString &cmd) 35 | ``` 36 | 37 | Executes a command on the remote server. 38 | 39 | **Parameters:** 40 | - `cmd`: Command string to execute 41 | 42 | **Example:** 43 | ```cpp 44 | SshProcess *proc = client->getChannel("ls"); 45 | proc->runCommand("ls -la /home"); 46 | ``` 47 | 48 | ### result 49 | 50 | ```cpp 51 | QByteArray result() 52 | ``` 53 | 54 | Returns the standard output from the executed command. 55 | 56 | **Returns:** Command output as QByteArray 57 | 58 | **Example:** 59 | ```cpp 60 | connect(proc, &SshProcess::finished, [=]() { 61 | QByteArray output = proc->result(); 62 | qDebug() << "Command output:" << output; 63 | }); 64 | ``` 65 | 66 | ### errMsg 67 | 68 | ```cpp 69 | QStringList errMsg() 70 | ``` 71 | 72 | Returns error messages from the command execution. 73 | 74 | **Returns:** List of error messages 75 | 76 | ### isError 77 | 78 | ```cpp 79 | bool isError() 80 | ``` 81 | 82 | Checks if an error occurred during command execution. 83 | 84 | **Returns:** `true` if error occurred, `false` otherwise 85 | 86 | ### close 87 | 88 | ```cpp 89 | void close() override 90 | ``` 91 | 92 | Closes the process channel. 93 | 94 | ## Signals 95 | 96 | ### finished 97 | 98 | ```cpp 99 | void finished() 100 | ``` 101 | 102 | Emitted when the command execution completes successfully. 103 | 104 | **Example:** 105 | ```cpp 106 | connect(proc, &SshProcess::finished, [=]() { 107 | qDebug() << "Command finished successfully"; 108 | qDebug() << proc->result(); 109 | }); 110 | ``` 111 | 112 | ### failed 113 | 114 | ```cpp 115 | void failed() 116 | ``` 117 | 118 | Emitted when the command execution fails. 119 | 120 | **Example:** 121 | ```cpp 122 | connect(proc, &SshProcess::failed, [=]() { 123 | qDebug() << "Command failed"; 124 | qDebug() << proc->errMsg(); 125 | }); 126 | ``` 127 | 128 | ## Usage Examples 129 | 130 | ### Basic Command Execution 131 | 132 | ```cpp 133 | SshClient *client = new SshClient("example"); 134 | client->setPassphrase("password"); 135 | 136 | QObject::connect(client, &SshClient::sshReady, [=]() { 137 | SshProcess *proc = client->getChannel("uptime"); 138 | proc->runCommand("uptime"); 139 | 140 | QObject::connect(proc, &SshProcess::finished, [=]() { 141 | qDebug() << "Server uptime:" << proc->result(); 142 | client->disconnectFromHost(); 143 | }); 144 | }); 145 | 146 | client->connectToHost("user", "server.com"); 147 | ``` 148 | 149 | ### Handling Command Errors 150 | 151 | ```cpp 152 | SshProcess *proc = client->getChannel("test"); 153 | 154 | connect(proc, &SshProcess::finished, [=]() { 155 | qDebug() << "Success:" << proc->result(); 156 | }); 157 | 158 | connect(proc, &SshProcess::failed, [=]() { 159 | qDebug() << "Failed with errors:"; 160 | for (const QString &err : proc->errMsg()) { 161 | qDebug() << " " << err; 162 | } 163 | }); 164 | 165 | proc->runCommand("cat /nonexistent/file"); 166 | ``` 167 | 168 | ### Sequential Commands 169 | 170 | ```cpp 171 | SshClient *client = new SshClient("sequential"); 172 | 173 | auto runCommands = [=]() { 174 | // First command 175 | SshProcess *proc1 = client->getChannel("cmd1"); 176 | proc1->runCommand("mkdir -p /tmp/test"); 177 | 178 | connect(proc1, &SshProcess::finished, [=]() { 179 | qDebug() << "Directory created"; 180 | 181 | // Second command 182 | SshProcess *proc2 = client->getChannel("cmd2"); 183 | proc2->runCommand("touch /tmp/test/file.txt"); 184 | 185 | connect(proc2, &SshProcess::finished, [=]() { 186 | qDebug() << "File created"; 187 | 188 | // Third command 189 | SshProcess *proc3 = client->getChannel("cmd3"); 190 | proc3->runCommand("ls -la /tmp/test"); 191 | 192 | connect(proc3, &SshProcess::finished, [=]() { 193 | qDebug() << "Directory contents:" << proc3->result(); 194 | client->disconnectFromHost(); 195 | }); 196 | }); 197 | }); 198 | }; 199 | 200 | connect(client, &SshClient::sshReady, runCommands); 201 | client->connectToHost("user", "server.com"); 202 | ``` 203 | 204 | ### Channel Reuse 205 | 206 | ```cpp 207 | SshProcess *proc = client->getChannel("reusable"); 208 | 209 | // First execution 210 | proc->runCommand("df -h"); 211 | connect(proc, &SshProcess::finished, [=]() { 212 | qDebug() << "Disk usage:" << proc->result(); 213 | 214 | // Reuse the same channel for another command 215 | proc->runCommand("free -m"); 216 | }); 217 | ``` 218 | 219 | ### Long-Running Commands 220 | 221 | ```cpp 222 | SshProcess *proc = client->getChannel("longrunning"); 223 | 224 | // Monitor state changes 225 | connect(proc, &SshChannel::stateChanged, [](SshChannel::ChannelState state) { 226 | switch(state) { 227 | case SshChannel::Exec: 228 | qDebug() << "Command executing..."; 229 | break; 230 | case SshChannel::Ready: 231 | qDebug() << "Command completed"; 232 | break; 233 | default: 234 | break; 235 | } 236 | }); 237 | 238 | proc->runCommand("sleep 10 && echo Done"); 239 | 240 | connect(proc, &SshProcess::finished, [=]() { 241 | qDebug() << proc->result(); 242 | }); 243 | ``` 244 | 245 | ### Parsing Command Output 246 | 247 | ```cpp 248 | SshProcess *proc = client->getChannel("parse"); 249 | proc->runCommand("ps aux"); 250 | 251 | connect(proc, &SshProcess::finished, [=]() { 252 | QString output = QString::fromUtf8(proc->result()); 253 | QStringList lines = output.split('\n'); 254 | 255 | qDebug() << "Running processes:"; 256 | for (const QString &line : lines) { 257 | if (!line.isEmpty()) { 258 | qDebug() << line; 259 | } 260 | } 261 | }); 262 | ``` 263 | 264 | ### Error Checking Pattern 265 | 266 | ```cpp 267 | void executeRemoteCommand(SshClient *client, const QString &cmd) { 268 | SshProcess *proc = client->getChannel("executor"); 269 | 270 | connect(proc, &SshProcess::finished, [=]() { 271 | if (proc->isError()) { 272 | qWarning() << "Command failed:"; 273 | for (const QString &err : proc->errMsg()) { 274 | qWarning() << " " << err; 275 | } 276 | } else { 277 | qDebug() << "Success:" << proc->result(); 278 | } 279 | }); 280 | 281 | connect(proc, &SshProcess::failed, [=]() { 282 | qWarning() << "Execution failed"; 283 | }); 284 | 285 | proc->runCommand(cmd); 286 | } 287 | ``` 288 | 289 | ## Best Practices 290 | 291 | 1. **Signal Connection**: Always connect to `finished` and `failed` signals before calling `runCommand()` 292 | 2. **Channel Naming**: Use descriptive channel names for easier debugging 293 | 3. **Channel Reuse**: Reuse channels for multiple commands to reduce overhead 294 | 4. **Error Handling**: Check `isError()` even when `finished` is emitted 295 | 5. **Output Processing**: Use `QString::fromUtf8()` when converting output to QString 296 | 6. **Resource Cleanup**: Close channels when done or reuse them efficiently 297 | 298 | ## Common Patterns 299 | 300 | ### Command with Timeout 301 | 302 | ```cpp 303 | SshProcess *proc = client->getChannel("timeout"); 304 | QTimer *timeout = new QTimer(); 305 | timeout->setSingleShot(true); 306 | timeout->setInterval(5000); // 5 seconds 307 | 308 | connect(timeout, &QTimer::timeout, [=]() { 309 | qWarning() << "Command timed out"; 310 | proc->close(); 311 | }); 312 | 313 | connect(proc, &SshProcess::finished, [=]() { 314 | timeout->stop(); 315 | qDebug() << proc->result(); 316 | }); 317 | 318 | timeout->start(); 319 | proc->runCommand("your-command"); 320 | ``` 321 | 322 | ### Command Queue 323 | 324 | ```cpp 325 | QQueue commands; 326 | commands.enqueue("ls -la"); 327 | commands.enqueue("pwd"); 328 | commands.enqueue("whoami"); 329 | 330 | void processNext(SshClient *client, QQueue &queue) { 331 | if (queue.isEmpty()) { 332 | client->disconnectFromHost(); 333 | return; 334 | } 335 | 336 | QString cmd = queue.dequeue(); 337 | SshProcess *proc = client->getChannel("queue"); 338 | proc->runCommand(cmd); 339 | 340 | connect(proc, &SshProcess::finished, [&]() { 341 | qDebug() << cmd << "result:" << proc->result(); 342 | processNext(client, queue); 343 | }); 344 | } 345 | ``` 346 | 347 | ## Limitations 348 | 349 | 1. **Interactive Commands**: Commands requiring interactive input are not supported 350 | 2. **PTY Allocation**: No pseudo-terminal allocation (some programs may behave differently) 351 | 3. **Output Buffering**: All output is buffered; streaming is not available 352 | 4. **Exit Codes**: Exit codes are not directly exposed 353 | 354 | ## See Also 355 | 356 | - [SshClient](sshclient.md) - Main client class 357 | - [SshChannel](sshchannel.md) - Base channel class 358 | - [Quick Start Guide](quickstart.md) - Getting started examples 359 | -------------------------------------------------------------------------------- /test/SshGui/sshconnectionform.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SshConnectionForm 4 | 5 | 6 | 7 | 0 8 | 0 9 | 919 10 | 478 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 0 23 | 24 | 25 | 26 | 27 | 28 | 29 | Host 30 | 31 | 32 | 33 | 34 | 35 | 36 | Port 37 | 38 | 39 | 40 | 41 | 42 | 43 | Login 44 | 45 | 46 | 47 | 48 | 49 | 50 | Password 51 | 52 | 53 | 54 | 55 | 56 | 57 | Connect 58 | 59 | 60 | 61 | 62 | 63 | 64 | 127.0.0.1 65 | 66 | 67 | 68 | 69 | 70 | 71 | 22 72 | 73 | 74 | 75 | 76 | 77 | 78 | fproriol 79 | 80 | 81 | 82 | 83 | 84 | 85 | users123 86 | 87 | 88 | 89 | 90 | 91 | 92 | Unconnected 93 | 94 | 95 | 96 | 97 | 98 | 99 | Channels: 100 | 101 | 102 | 103 | 104 | 105 | 106 | true 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | false 117 | 118 | 119 | 120 | 0 121 | 0 122 | 123 | 124 | 125 | QTabWidget::North 126 | 127 | 128 | 0 129 | 130 | 131 | 132 | DirectTunnel 133 | 134 | 135 | 136 | 137 | 138 | Hostname: 139 | 140 | 141 | 142 | 143 | 144 | 145 | 127.0.0.1 146 | 147 | 148 | 149 | 150 | 151 | 152 | Qt::Horizontal 153 | 154 | 155 | 156 | 40 157 | 20 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | Port 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | Qt::Horizontal 176 | 177 | 178 | 179 | 40 180 | 20 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | Create 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | ForwardTunnel 197 | 198 | 199 | 200 | 201 | 202 | Listen Port: 203 | 204 | 205 | 206 | 207 | 208 | 209 | 0 210 | 211 | 212 | 213 | 214 | 215 | 216 | Qt::Horizontal 217 | 218 | 219 | 220 | 59 221 | 20 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | Target: 230 | 231 | 232 | 233 | 234 | 235 | 236 | 127.0.0.1 237 | 238 | 239 | 240 | 241 | 242 | 243 | Qt::Horizontal 244 | 245 | 246 | 247 | 58 248 | 20 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | Target Port: 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | Qt::Horizontal 267 | 268 | 269 | 270 | 59 271 | 20 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | Create 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 0 295 | 0 296 | 297 | 298 | 299 | Tunnels: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | -------------------------------------------------------------------------------- /doc/installation.md: -------------------------------------------------------------------------------- 1 | # QtSsh Installation and Building Guide 2 | 3 | ## Overview 4 | 5 | QtSsh is a Qt library wrapper for libssh2 that provides SSH functionality in Qt applications. This guide covers installation, building, and integration into your projects. 6 | 7 | ## Prerequisites 8 | 9 | ### Required Dependencies 10 | 11 | - **Qt 5.x**: Qt Core and Qt Network modules 12 | - **libssh2**: SSH2 protocol library 13 | - **CMake** 3.13 or higher (for CMake builds) 14 | - **qmake** (for qmake builds) 15 | - C++11 compatible compiler 16 | 17 | ### Optional Dependencies 18 | 19 | - **OpenSSL**: Required by libssh2 for encryption 20 | - **zlib**: For compression support in libssh2 21 | 22 | ## Installing libssh2 23 | 24 | ### Ubuntu/Debian 25 | 26 | ```bash 27 | sudo apt-get update 28 | sudo apt-get install libssh2-1-dev 29 | ``` 30 | 31 | ### Fedora/RHEL/CentOS 32 | 33 | ```bash 34 | sudo dnf install libssh2-devel 35 | # or on older systems: 36 | sudo yum install libssh2-devel 37 | ``` 38 | 39 | ### macOS 40 | 41 | Using Homebrew: 42 | ```bash 43 | brew install libssh2 44 | ``` 45 | 46 | Using MacPorts: 47 | ```bash 48 | sudo port install libssh2 49 | ``` 50 | 51 | ### Windows 52 | 53 | #### Using vcpkg 54 | 55 | ```bash 56 | vcpkg install libssh2 57 | ``` 58 | 59 | #### Manual Installation 60 | 61 | 1. Download libssh2 from [https://www.libssh2.org/](https://www.libssh2.org/) 62 | 2. Build and install following the libssh2 documentation 63 | 3. Ensure the library and headers are in your compiler's search paths 64 | 65 | ## Building QtSsh 66 | 67 | QtSsh can be built using either CMake or qmake. 68 | 69 | ### Method 1: CMake (Recommended) 70 | 71 | #### Basic Build 72 | 73 | ```bash 74 | # Clone or extract QtSsh 75 | cd qtssh 76 | 77 | # Create build directory 78 | mkdir build 79 | cd build 80 | 81 | # Configure 82 | cmake .. 83 | 84 | # Build 85 | cmake --build . 86 | 87 | # Install (optional) 88 | sudo cmake --install . 89 | ``` 90 | 91 | #### Build Options 92 | 93 | **Build static library:** 94 | ```bash 95 | cmake -DBUILD_STATIC=ON .. 96 | ``` 97 | 98 | **Build with examples:** 99 | ```bash 100 | cmake -DWITH_EXAMPLES=ON .. 101 | ``` 102 | 103 | **Specify custom libssh2 location:** 104 | ```bash 105 | cmake -DWITH_SSH_LIBRARIES=/path/to/libssh2.so \ 106 | -DWITH_SSH_HEADERS=/path/to/libssh2/include .. 107 | ``` 108 | 109 | **Complete example with all options:** 110 | ```bash 111 | mkdir build && cd build 112 | cmake -DBUILD_STATIC=OFF \ 113 | -DWITH_EXAMPLES=ON \ 114 | -DCMAKE_INSTALL_PREFIX=/usr/local \ 115 | -DCMAKE_BUILD_TYPE=Release \ 116 | .. 117 | cmake --build . -j$(nproc) 118 | sudo cmake --install . 119 | ``` 120 | 121 | ### Method 2: qmake 122 | 123 | #### As a Git Submodule 124 | 125 | 1. **Add QtSsh as submodule:** 126 | ```bash 127 | cd your-project 128 | git submodule add https://github.com/[repo]/qtssh.git 129 | git submodule update --init --recursive 130 | ``` 131 | 132 | 2. **In your .pro file:** 133 | ```qmake 134 | include(qtssh/QtSsh.pri) 135 | 136 | # Link with libssh2 137 | unix { 138 | LIBS += -lssh2 139 | } 140 | 141 | win32 { 142 | LIBS += -lssh2 143 | } 144 | ``` 145 | 146 | 3. **Build your project:** 147 | ```bash 148 | qmake 149 | make 150 | ``` 151 | 152 | #### Standalone Build 153 | 154 | ```bash 155 | cd qtssh 156 | qmake QtSsh.pro 157 | make 158 | ``` 159 | 160 | ## Integration into Your Project 161 | 162 | ### CMake Integration 163 | 164 | #### Option 1: Installed QtSsh 165 | 166 | If QtSsh is installed system-wide: 167 | 168 | ```cmake 169 | cmake_minimum_required(VERSION 3.13) 170 | project(MyApp) 171 | 172 | find_package(Qt5 REQUIRED COMPONENTS Core Network) 173 | find_package(qtssh REQUIRED) 174 | 175 | add_executable(myapp main.cpp) 176 | target_link_libraries(myapp Qt5::Core Qt5::Network qtssh) 177 | ``` 178 | 179 | #### Option 2: Embedded QtSsh 180 | 181 | Add QtSsh as a subdirectory: 182 | 183 | ```cmake 184 | cmake_minimum_required(VERSION 3.13) 185 | project(MyApp) 186 | 187 | find_package(Qt5 REQUIRED COMPONENTS Core Network) 188 | 189 | # Add QtSsh 190 | add_subdirectory(qtssh) 191 | 192 | add_executable(myapp main.cpp) 193 | target_link_libraries(myapp Qt5::Core Qt5::Network qtssh) 194 | ``` 195 | 196 | #### Option 3: External Project 197 | 198 | ```cmake 199 | include(qtssh/qtssh.cmake) 200 | 201 | add_executable(myapp main.cpp) 202 | target_link_libraries(myapp qtssh) 203 | ``` 204 | 205 | ### qmake Integration 206 | 207 | In your `.pro` file: 208 | 209 | ```qmake 210 | QT += core network 211 | 212 | # Include QtSsh 213 | include(path/to/qtssh/QtSsh.pri) 214 | 215 | # Link with libssh2 216 | unix { 217 | LIBS += -lssh2 218 | } 219 | 220 | win32 { 221 | # Adjust path as needed 222 | LIBS += -lssh2 223 | # Or for static: 224 | # LIBS += -Lc:/path/to/libssh2/lib -lssh2 225 | } 226 | 227 | SOURCES += main.cpp 228 | ``` 229 | 230 | ### Manual Integration 231 | 232 | 1. **Add QtSsh source files to your project:** 233 | - Copy all files from `qtssh/` directory 234 | - Add them to your project's build system 235 | 236 | 2. **Include headers:** 237 | ```cpp 238 | #include "sshclient.h" 239 | #include "sshprocess.h" 240 | #include "sshsftp.h" 241 | // etc. 242 | ``` 243 | 244 | 3. **Link with libssh2:** 245 | - Add `-lssh2` to linker flags 246 | - Ensure libssh2 headers are in include path 247 | 248 | ## Project Structure 249 | 250 | ``` 251 | qtssh/ 252 | ├── CMakeLists.txt # CMake configuration 253 | ├── QtSsh.pri # qmake configuration 254 | ├── qtssh.cmake # CMake helper 255 | ├── LICENSE # BSD 3-Clause License 256 | ├── README.md # Basic readme 257 | ├── doc/ # Documentation (this folder) 258 | ├── qtssh/ # Library source code 259 | │ ├── sshclient.h 260 | │ ├── sshclient.cpp 261 | │ ├── sshprocess.h 262 | │ ├── sshprocess.cpp 263 | │ ├── sshsftp.h 264 | │ ├── sshsftp.cpp 265 | │ ├── sshscpget.h 266 | │ ├── sshscpget.cpp 267 | │ ├── sshscpsend.h 268 | │ ├── sshscpsend.cpp 269 | │ ├── sshtunnelin.h 270 | │ ├── sshtunnelin.cpp 271 | │ ├── sshtunnelout.h 272 | │ ├── sshtunnelout.cpp 273 | │ └── ... 274 | └── test/ # Test applications 275 | ├── SshConsole/ # Console test app 276 | ├── SshGui/ # GUI test app 277 | └── TestSsh/ # Unit tests 278 | ``` 279 | 280 | ## Verifying Installation 281 | 282 | ### Simple Test Program 283 | 284 | Create a file `test.cpp`: 285 | 286 | ```cpp 287 | #include 288 | #include 289 | #include "sshclient.h" 290 | 291 | int main(int argc, char *argv[]) { 292 | QCoreApplication app(argc, argv); 293 | 294 | SshClient client("test"); 295 | qDebug() << "QtSsh is working!"; 296 | qDebug() << "Client name:" << client.getName(); 297 | 298 | return 0; 299 | } 300 | ``` 301 | 302 | #### Build with CMake: 303 | 304 | `CMakeLists.txt`: 305 | ```cmake 306 | cmake_minimum_required(VERSION 3.13) 307 | project(test) 308 | 309 | find_package(Qt5 REQUIRED COMPONENTS Core Network) 310 | find_package(qtssh REQUIRED) 311 | 312 | add_executable(test test.cpp) 313 | target_link_libraries(test Qt5::Core Qt5::Network qtssh) 314 | ``` 315 | 316 | Build: 317 | ```bash 318 | mkdir build && cd build 319 | cmake .. 320 | cmake --build . 321 | ./test 322 | ``` 323 | 324 | #### Build with qmake: 325 | 326 | `test.pro`: 327 | ```qmake 328 | QT += core network 329 | CONFIG += console 330 | CONFIG -= app_bundle 331 | 332 | include(../qtssh/QtSsh.pri) 333 | 334 | SOURCES += test.cpp 335 | 336 | unix { 337 | LIBS += -lssh2 338 | } 339 | ``` 340 | 341 | Build: 342 | ```bash 343 | qmake 344 | make 345 | ./test 346 | ``` 347 | 348 | ## Platform-Specific Notes 349 | 350 | ### Linux 351 | 352 | - Standard build process works well 353 | - libssh2 usually available in package managers 354 | - No special configuration needed 355 | 356 | ### macOS 357 | 358 | - Use Homebrew or MacPorts for dependencies 359 | - May need to specify library paths: 360 | ```bash 361 | cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/libssh2 .. 362 | ``` 363 | 364 | ### Windows 365 | 366 | - Ensure libssh2 DLL is in PATH or application directory 367 | - For static builds, define `LIBSSH2_STATIC` if needed 368 | - Visual Studio, MinGW, and MSVC are supported 369 | 370 | **Visual Studio example:** 371 | ```bash 372 | mkdir build && cd build 373 | cmake -G "Visual Studio 16 2019" .. 374 | cmake --build . --config Release 375 | ``` 376 | 377 | **MinGW example:** 378 | ```bash 379 | mkdir build && cd build 380 | cmake -G "MinGW Makefiles" .. 381 | cmake --build . 382 | ``` 383 | 384 | ## Troubleshooting 385 | 386 | ### libssh2 Not Found 387 | 388 | **Error:** `Could not find libssh2` 389 | 390 | **Solution:** 391 | ```bash 392 | # Specify libssh2 location 393 | cmake -DWITH_SSH_LIBRARIES=/path/to/libssh2 \ 394 | -DWITH_SSH_HEADERS=/path/to/libssh2/include .. 395 | ``` 396 | 397 | ### Qt Not Found 398 | 399 | **Error:** `Could not find Qt5` 400 | 401 | **Solution:** 402 | ```bash 403 | # Specify Qt location 404 | cmake -DCMAKE_PREFIX_PATH=/path/to/Qt/5.15.2/gcc_64 .. 405 | ``` 406 | 407 | ### Linker Errors on Windows 408 | 409 | **Error:** Unresolved symbols from libssh2 410 | 411 | **Solution:** 412 | - Ensure libssh2.lib is linked 413 | - Check if static/dynamic linking matches 414 | - Add OpenSSL and zlib if libssh2 requires them 415 | 416 | ### Runtime Errors 417 | 418 | **Error:** `Cannot load library libssh2` 419 | 420 | **Solution:** 421 | - Ensure libssh2 shared library is in system path 422 | - On Windows: Copy libssh2.dll to application directory 423 | - On Linux: Check `LD_LIBRARY_PATH` 424 | - On macOS: Check `DYLD_LIBRARY_PATH` 425 | 426 | ## Building Documentation 427 | 428 | Documentation is written in Markdown and located in the `doc/` folder. 429 | 430 | To convert to HTML (requires a Markdown processor): 431 | ```bash 432 | # Using pandoc 433 | for file in doc/*.md; do 434 | pandoc "$file" -o "${file%.md}.html" 435 | done 436 | ``` 437 | 438 | ## Running Tests 439 | 440 | ```bash 441 | cd test/TestSsh 442 | qmake 443 | make 444 | ./TestSsh 445 | ``` 446 | 447 | Or with CMake: 448 | ```bash 449 | mkdir build && cd build 450 | cmake -DWITH_EXAMPLES=ON .. 451 | cmake --build . 452 | ./bin/TestSsh 453 | ``` 454 | 455 | ## Next Steps 456 | 457 | - See [Quick Start Guide](quickstart.md) for your first QtSsh application 458 | - Read [API Documentation](sshclient.md) for detailed class references 459 | - Check out example applications in the `test/` directory 460 | 461 | ## License 462 | 463 | QtSsh is licensed under the BSD 3-Clause License. See [LICENSE](../LICENSE) file for details. 464 | 465 | ## Support 466 | 467 | - Report issues on GitHub issue tracker 468 | - Check example applications for usage patterns 469 | - Consult libssh2 documentation for underlying SSH functionality 470 | -------------------------------------------------------------------------------- /qtssh/sshsftp.cpp: -------------------------------------------------------------------------------- 1 | #include "sshsftp.h" 2 | #include "sshclient.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include "sshsftpcommandsend.h" 8 | #include "sshsftpcommandget.h" 9 | #include "sshsftpcommandreaddir.h" 10 | #include "sshsftpcommandmkdir.h" 11 | #include "sshsftpcommandunlink.h" 12 | #include "sshsftpcommandfileinfo.h" 13 | 14 | Q_LOGGING_CATEGORY(logsshsftp, "ssh.sftp", QtWarningMsg) 15 | 16 | #define DEBUGCH qCDebug(logsshsftp) << m_name 17 | 18 | SshSFtp::SshSFtp(const QString &name, SshClient *client): 19 | SshChannel(name, client) 20 | 21 | { 22 | QObject::connect(client, &SshClient::sshDataReceived, this, &SshSFtp::sshDataReceived, Qt::QueuedConnection); 23 | QObject::connect(this, &SshSFtp::sendEvent, this, &SshSFtp::_eventLoop, Qt::QueuedConnection); 24 | } 25 | 26 | SshSFtp::~SshSFtp() 27 | { 28 | DEBUGCH << "Free SshSFtp (destructor)"; 29 | } 30 | 31 | void SshSFtp::close() 32 | { 33 | DEBUGCH << "Close SshSFtp asked"; 34 | if(channelState() != ChannelState::Error) 35 | { 36 | setChannelState(ChannelState::Close); 37 | } 38 | emit sendEvent(); 39 | } 40 | 41 | 42 | QString SshSFtp::send(const QString &source, QString dest) 43 | { 44 | DEBUGCH << "send(" << source << ", " << dest << ")"; 45 | QFileInfo src(source); 46 | if(dest.endsWith("/")) 47 | { 48 | if(!isDir(dest)) 49 | { 50 | mkpath(dest); 51 | } 52 | dest += src.fileName(); 53 | } 54 | 55 | QString s(source); 56 | s.replace("qrc:/", ":/"); 57 | 58 | SshSftpCommandSend cmd(s,dest,*this); 59 | if(processCmd(&cmd)) 60 | { 61 | return dest; 62 | } 63 | return QString(); 64 | } 65 | 66 | bool SshSFtp::get(const QString &source, QString dest, bool override) 67 | { 68 | DEBUGCH << "get(" << source << ", " << dest << ", " << override << ")"; 69 | QFileInfo src(source); 70 | if(dest.endsWith("/")) 71 | { 72 | dest += src.fileName(); 73 | } 74 | 75 | 76 | QString original(dest); 77 | QFile fout(dest); 78 | if(!override) 79 | { 80 | if(fout.exists()) 81 | { 82 | QString newpath; 83 | int i = 1; 84 | do { 85 | newpath = QString("%1.%2").arg(dest).arg(i); 86 | fout.setFileName(newpath); 87 | ++i; 88 | } while(fout.exists()); 89 | dest = newpath; 90 | } 91 | } 92 | 93 | SshSftpCommandGet cmd(fout, source, *this); 94 | bool ret = processCmd(&cmd); 95 | 96 | if(!ret) 97 | return false; 98 | 99 | /* Remove file if is the same that original */ 100 | if(dest != original) 101 | { 102 | QCryptographicHash hash1( QCryptographicHash::Md5 ); 103 | QCryptographicHash hash2( QCryptographicHash::Md5 ); 104 | QFile f1( original ); 105 | if ( f1.open( QIODevice::ReadOnly ) ) { 106 | hash1.addData( f1.readAll() ); 107 | } 108 | QByteArray sig1 = hash1.result(); 109 | QFile f2( dest ); 110 | if ( f2.open( QIODevice::ReadOnly ) ) { 111 | hash2.addData( f2.readAll() ); 112 | } 113 | QByteArray sig2 = hash2.result(); 114 | if(sig1 == sig2) 115 | { 116 | f2.remove(); 117 | } 118 | } 119 | return true; 120 | } 121 | 122 | int SshSFtp::mkdir(const QString &dest, int mode) 123 | { 124 | SshSftpCommandMkdir cmd(dest, mode, *this); 125 | DEBUGCH << "mkdir(" << dest << "," << mode << ")"; 126 | processCmd(&cmd); 127 | DEBUGCH << "mkdir(" << dest << ") = " << ((cmd.error())?("FAIL"):("OK")); 128 | if(cmd.error()) return -1; 129 | return 0; 130 | } 131 | 132 | QStringList SshSFtp::readdir(const QString &d) 133 | { 134 | SshSftpCommandReadDir cmd(d, *this); 135 | DEBUGCH << "readdir(" << d << ")"; 136 | processCmd(&cmd); 137 | DEBUGCH << "readdir(" << d << ") = " << cmd.result(); 138 | return cmd.result(); 139 | } 140 | 141 | bool SshSFtp::isDir(const QString &d) 142 | { 143 | DEBUGCH << "isDir(" << d << ")"; 144 | LIBSSH2_SFTP_ATTRIBUTES fileinfo = getFileInfo(d); 145 | qCDebug(logsshsftp) << "isDir(" << d << ") = " << LIBSSH2_SFTP_S_ISDIR(fileinfo.permissions); 146 | return LIBSSH2_SFTP_S_ISDIR(fileinfo.permissions); 147 | } 148 | 149 | bool SshSFtp::isFile(const QString &d) 150 | { 151 | DEBUGCH << "isFile(" << d << ")"; 152 | LIBSSH2_SFTP_ATTRIBUTES fileinfo = getFileInfo(d); 153 | qCDebug(logsshsftp) << "isFile(" << d << ") = " << LIBSSH2_SFTP_S_ISREG(fileinfo.permissions); 154 | return LIBSSH2_SFTP_S_ISREG(fileinfo.permissions); 155 | } 156 | 157 | int SshSFtp::mkpath(const QString &dest) 158 | { 159 | DEBUGCH << "mkpath(" << dest << ")"; 160 | if(isDir(dest)) return true; 161 | QStringList d = dest.split("/"); 162 | d.pop_back(); 163 | if(mkpath(d.join("/"))) 164 | { 165 | mkdir(dest); 166 | } 167 | if(isDir(dest)) return true; 168 | return false; 169 | } 170 | 171 | bool SshSFtp::unlink(const QString &d) 172 | { 173 | SshSftpCommandUnlink cmd(d, *this); 174 | DEBUGCH << "unlink(" << d << "," << d << ")"; 175 | processCmd(&cmd); 176 | DEBUGCH << "unlink(" << d << ") = " << ((cmd.error())?("FAIL"):("OK")); 177 | if(cmd.error()) return -1; 178 | return 0; 179 | } 180 | 181 | quint64 SshSFtp::filesize(const QString &d) 182 | { 183 | DEBUGCH << "filesize(" << d << ")"; 184 | return getFileInfo(d).filesize; 185 | } 186 | 187 | void SshSFtp::sshDataReceived() 188 | { 189 | // Nothing to do 190 | _eventLoop(); 191 | } 192 | 193 | void SshSFtp::_eventLoop() 194 | { 195 | switch(channelState()) 196 | { 197 | case Openning: 198 | { 199 | if ( ! m_sshClient->takeChannelCreationMutex(this) ) 200 | { 201 | return; 202 | } 203 | m_sftpSession = libssh2_sftp_init(m_sshClient->session()); 204 | m_sshClient->releaseChannelCreationMutex(this); 205 | if(m_sftpSession == nullptr) 206 | { 207 | char *emsg; 208 | int size; 209 | int ret = libssh2_session_last_error(m_sshClient->session(), &emsg, &size, 0); 210 | if(ret == LIBSSH2_ERROR_EAGAIN) 211 | { 212 | return; 213 | } 214 | if(!m_error) 215 | { 216 | qCWarning(logsshsftp) << "Create sftp session failed " << QString(emsg); 217 | m_error = true; 218 | } 219 | m_errMsg << QString::fromUtf8(emsg, size); 220 | setChannelState(ChannelState::Error); 221 | qCWarning(logsshsftp) << "Channel session open failed"; 222 | return; 223 | } 224 | DEBUGCH << "Channel session opened"; 225 | setChannelState(ChannelState::Exec); 226 | } 227 | 228 | FALLTHROUGH; case Exec: 229 | { 230 | setChannelState(ChannelState::Ready); 231 | } 232 | 233 | 234 | FALLTHROUGH; case Ready: 235 | { 236 | if(m_currentCmd == nullptr && m_cmd.size() > 0) 237 | { 238 | m_currentCmd = m_cmd.first(); 239 | DEBUGCH << "Start process next command:" << m_currentCmd->name(); 240 | m_cmd.pop_front(); 241 | } 242 | if(m_currentCmd) 243 | { 244 | DEBUGCH << "Continue process current command:" << m_currentCmd->name(); 245 | m_currentCmd->process(); 246 | if(m_currentCmd->state() == SshSftpCommand::CommandState::Terminate || m_currentCmd->state() == SshSftpCommand::CommandState::Error) 247 | { 248 | DEBUGCH << "Finish process command:" << m_currentCmd->name(); 249 | if (m_currentCmd->state() == SshSftpCommand::CommandState::Error) 250 | { 251 | if (m_currentCmd->errMsg().size() > 0) 252 | m_errMsg.append(m_currentCmd->errMsg()); 253 | m_error = true; 254 | } 255 | m_currentCmd = nullptr; 256 | emit cmdEvent(); 257 | emit sendEvent(); 258 | } 259 | } 260 | break; 261 | } 262 | 263 | case Close: 264 | { 265 | DEBUGCH << "closeChannel"; 266 | if(libssh2_sftp_shutdown(m_sftpSession) != 0) 267 | { 268 | return; 269 | } 270 | setChannelState(ChannelState::WaitClose); 271 | } 272 | 273 | FALLTHROUGH; case WaitClose: 274 | { 275 | setChannelState(ChannelState::Freeing); 276 | } 277 | 278 | FALLTHROUGH; case Freeing: 279 | { 280 | DEBUGCH << "free Channel"; 281 | setChannelState(ChannelState::Free); 282 | QObject::disconnect(m_sshClient, &SshClient::sshDataReceived, this, &SshSFtp::sshDataReceived); 283 | return; 284 | } 285 | 286 | case Free: 287 | { 288 | DEBUGCH << "Channel is free"; 289 | return; 290 | } 291 | 292 | case Error: 293 | { 294 | DEBUGCH << "Channel is in error state"; 295 | setChannelState(Free); 296 | return; 297 | } 298 | } 299 | } 300 | 301 | LIBSSH2_SFTP *SshSFtp::getSftpSession() const 302 | { 303 | return m_sftpSession; 304 | } 305 | 306 | bool SshSFtp::processCmd(SshSftpCommand *cmd) 307 | { 308 | QEventLoop wait(this); 309 | QObject::connect(this, &SshSFtp::stateChanged, &wait, &QEventLoop::quit); 310 | QObject::connect(this, &SshSFtp::cmdEvent, &wait, &QEventLoop::quit); 311 | 312 | m_cmd.push_back(cmd); 313 | emit sendEvent(); 314 | while(channelState() <= ChannelState::Ready && cmd->state() != SshSftpCommand::CommandState::Terminate && cmd->state() != SshSftpCommand::CommandState::Error) 315 | { 316 | wait.exec(); 317 | } 318 | return (cmd->state() == SshSftpCommand::CommandState::Terminate); 319 | } 320 | 321 | bool SshSFtp::isError() 322 | { 323 | return m_error; 324 | } 325 | 326 | QStringList SshSFtp::errMsg() 327 | { 328 | return m_errMsg; 329 | } 330 | 331 | LIBSSH2_SFTP_ATTRIBUTES SshSFtp::getFileInfo(const QString &path) 332 | { 333 | if(!m_fileinfo.contains(path)) 334 | { 335 | SshSftpCommandFileInfo cmd(path, *this); 336 | DEBUGCH << "fileinfo(" << path << ")"; 337 | processCmd(&cmd); 338 | DEBUGCH << "fileinfo(" << path << ") = " << ((cmd.error())?("FAIL"):("OK")); 339 | LIBSSH2_SFTP_ATTRIBUTES fileinfo = cmd.fileinfo(); 340 | m_fileinfo[path] = fileinfo; 341 | } 342 | return m_fileinfo[path]; 343 | } 344 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QtSsh 2 | 3 | A Qt library wrapper for libssh2 providing comprehensive SSH functionality including remote command execution, file transfer (SFTP/SCP), and SSH tunneling. 4 | 5 | ## Features 6 | 7 | - **Remote Command Execution**: Execute commands on remote servers via SSH 8 | - **SFTP Support**: Full SFTP implementation for file operations 9 | - Upload and download files 10 | - Directory operations (create, list, remove) 11 | - File attribute queries 12 | - **SCP File Transfer**: Secure file copy with progress monitoring 13 | - **SSH Tunneling**: Both local and remote port forwarding 14 | - Local forwarding (access remote services) 15 | - Remote forwarding (expose local services) 16 | - **Authentication**: Support for both password and public key authentication 17 | - **Qt Integration**: Native Qt signals/slots and event-driven architecture 18 | - **Channel Management**: Reusable channels for efficient operations 19 | 20 | ## Quick Example 21 | 22 | ```cpp 23 | #include 24 | #include "sshclient.h" 25 | #include "sshprocess.h" 26 | 27 | int main(int argc, char *argv[]) { 28 | QCoreApplication app(argc, argv); 29 | 30 | SshClient *client = new SshClient("example"); 31 | client->setPassphrase("password"); 32 | 33 | QObject::connect(client, &SshClient::sshReady, [=]() { 34 | SshProcess *proc = client->getChannel("cmd"); 35 | proc->runCommand("ls -la"); 36 | 37 | QObject::connect(proc, &SshProcess::finished, [=]() { 38 | qDebug() << proc->result(); 39 | client->disconnectFromHost(); 40 | }); 41 | }); 42 | 43 | QObject::connect(client, &SshClient::sshDisconnected, [&]() { 44 | app.quit(); 45 | }); 46 | 47 | client->connectToHost("user", "server.com"); 48 | return app.exec(); 49 | } 50 | ``` 51 | 52 | ## Documentation 53 | 54 | ### Getting Started 55 | 56 | - **[Installation Guide](doc/installation.md)** - Build and integrate QtSsh into your project 57 | - **[Quick Start Guide](doc/quickstart.md)** - Your first QtSsh application with examples 58 | - **[Error Handling](doc/errors.md)** - Comprehensive error handling guide 59 | 60 | ### API Reference 61 | 62 | - **[SshClient](doc/sshclient.md)** - Main SSH client class for connection management 63 | - **[SshChannel](doc/sshchannel.md)** - Base class for all channel types 64 | - **[SshProcess](doc/sshprocess.md)** - Remote command execution 65 | - **[SshSFtp](doc/sshsftp.md)** - SFTP file transfer and operations 66 | - **[SshScpGet/SshScpSend](doc/sshscp.md)** - SCP file transfer with progress 67 | - **[SSH Tunneling](doc/tunneling.md)** - Local and remote port forwarding 68 | 69 | ## Requirements 70 | 71 | - Qt 5.x (Core and Network modules) 72 | - libssh2 73 | - C++11 compatible compiler 74 | - CMake 3.13+ or qmake 75 | 76 | ## Installation 77 | 78 | ### Quick Install 79 | 80 | ```bash 81 | # Install dependencies (Ubuntu/Debian) 82 | sudo apt-get install libssh2-1-dev qt5-default 83 | 84 | # Build QtSsh 85 | mkdir build && cd build 86 | cmake .. 87 | cmake --build . 88 | sudo cmake --install . 89 | ``` 90 | 91 | ### Integration into Your Project 92 | 93 | #### CMake 94 | 95 | ```cmake 96 | find_package(qtssh REQUIRED) 97 | target_link_libraries(your_app qtssh) 98 | ``` 99 | 100 | #### qmake 101 | 102 | ```qmake 103 | include(path/to/QtSsh.pri) 104 | LIBS += -lssh2 105 | ``` 106 | 107 | See the [Installation Guide](doc/installation.md) for detailed instructions. 108 | 109 | ## Usage Examples 110 | 111 | ### Execute Remote Command 112 | 113 | ```cpp 114 | SshProcess *proc = client->getChannel("command"); 115 | proc->runCommand("uptime"); 116 | 117 | connect(proc, &SshProcess::finished, [=]() { 118 | qDebug() << "Output:" << proc->result(); 119 | }); 120 | ``` 121 | 122 | ### Upload File via SFTP 123 | 124 | ```cpp 125 | SshSFtp *sftp = client->getChannel("upload"); 126 | QString error = sftp->send("/local/file.txt", "/remote/file.txt"); 127 | 128 | if (error.isEmpty()) { 129 | qDebug() << "Upload successful"; 130 | } 131 | ``` 132 | 133 | ### Download File with Progress 134 | 135 | ```cpp 136 | SshScpGet *scp = client->getChannel("download"); 137 | 138 | connect(scp, &SshScpGet::progress, [](qint64 received, qint64 total) { 139 | qDebug() << "Progress:" << (received * 100 / total) << "%"; 140 | }); 141 | 142 | connect(scp, &SshScpGet::finished, []() { 143 | qDebug() << "Download complete"; 144 | }); 145 | 146 | scp->get("/remote/file.zip", "/local/file.zip"); 147 | ``` 148 | 149 | ### Create SSH Tunnel 150 | 151 | ```cpp 152 | SshTunnelOut *tunnel = client->getChannel("db-tunnel"); 153 | tunnel->listen(3306, "mysql.internal.network"); 154 | 155 | qDebug() << "MySQL tunnel ready at localhost:3306"; 156 | ``` 157 | 158 | ## Project Structure 159 | 160 | ``` 161 | qtssh/ 162 | ├── CMakeLists.txt # CMake build configuration 163 | ├── QtSsh.pri # qmake configuration 164 | ├── README.md # This file 165 | ├── LICENSE # BSD 3-Clause License 166 | ├── doc/ # Documentation 167 | │ ├── installation.md # Build and installation guide 168 | │ ├── quickstart.md # Getting started tutorial 169 | │ ├── errors.md # Error handling guide 170 | │ ├── sshclient.md # SshClient API reference 171 | │ ├── sshchannel.md # SshChannel API reference 172 | │ ├── sshprocess.md # SshProcess API reference 173 | │ ├── sshsftp.md # SshSFtp API reference 174 | │ ├── sshscp.md # SshScp API reference 175 | │ └── tunneling.md # Tunneling guide 176 | ├── qtssh/ # Library source code 177 | └── test/ # Example applications 178 | ├── SshConsole/ # Console SSH client 179 | ├── SshGui/ # GUI SSH client 180 | └── TestSsh/ # Unit tests 181 | ``` 182 | 183 | ## Key Classes 184 | 185 | | Class | Purpose | 186 | |-------|---------| 187 | | `SshClient` | Main SSH connection management | 188 | | `SshProcess` | Execute remote commands | 189 | | `SshSFtp` | SFTP file operations | 190 | | `SshScpGet` | Download files via SCP | 191 | | `SshScpSend` | Upload files via SCP | 192 | | `SshTunnelOut` | Local port forwarding | 193 | | `SshTunnelIn` | Remote port forwarding | 194 | | `SshChannel` | Base class for all channels | 195 | | `SshKey` | SSH key management | 196 | 197 | ## Authentication Methods 198 | 199 | ### Password Authentication 200 | 201 | ```cpp 202 | client->setPassphrase("your-password"); 203 | client->connectToHost("username", "hostname"); 204 | ``` 205 | 206 | ### Public Key Authentication 207 | 208 | ```cpp 209 | client->setKeys("~/.ssh/id_rsa.pub", "~/.ssh/id_rsa"); 210 | client->connectToHost("username", "hostname"); 211 | ``` 212 | 213 | ## License 214 | 215 | QtSsh is licensed under the **BSD 3-Clause License**. 216 | 217 | ``` 218 | Copyright (c) 2018, Proriol Fabien 219 | All rights reserved. 220 | ``` 221 | 222 | See [LICENSE](LICENSE) file for full license text. 223 | 224 | ## Contributing 225 | 226 | This project is designed to be integrated as a git submodule in larger projects. 227 | 228 | To use QtSsh in your project: 229 | 230 | ```bash 231 | git submodule add qtssh 232 | git submodule update --init --recursive 233 | ``` 234 | 235 | Then include in your build: 236 | - **qmake**: `include(qtssh/QtSsh.pri)` 237 | - **CMake**: `add_subdirectory(qtssh)` or use `qtssh.cmake` 238 | 239 | ## Version 240 | 241 | Current version: **1.3.4** 242 | 243 | ## Dependencies 244 | 245 | - **Qt 5.x**: Core and Network modules 246 | - **libssh2**: SSH2 protocol implementation 247 | - Typically requires OpenSSL for encryption 248 | - Optional zlib support for compression 249 | 250 | ## Platform Support 251 | 252 | - Linux (tested and recommended) 253 | - macOS 254 | - Windows (MinGW, MSVC) 255 | 256 | ## Example Applications 257 | 258 | The `test/` directory contains example applications: 259 | 260 | - **SshConsole**: Command-line SSH client 261 | - **SshGui**: GUI application demonstrating all features 262 | - **TestSsh**: Unit tests and examples 263 | 264 | ## Common Use Cases 265 | 266 | - **Remote Server Management**: Execute commands on remote servers 267 | - **Automated Deployments**: Upload files and run deployment scripts 268 | - **Database Tunneling**: Secure access to remote databases 269 | - **File Synchronization**: Backup and sync files between systems 270 | - **Network Access**: Access internal network services through SSH gateway 271 | - **Development Tools**: Remote debugging, log access, server monitoring 272 | 273 | ## Support and Resources 274 | 275 | - **Documentation**: See `doc/` directory for comprehensive guides 276 | - **Examples**: Check `test/` directory for working examples 277 | - **libssh2 Documentation**: [https://www.libssh2.org/](https://www.libssh2.org/) 278 | - **Qt Documentation**: [https://doc.qt.io/](https://doc.qt.io/) 279 | 280 | ## Building from Source 281 | 282 | ### Using CMake 283 | 284 | ```bash 285 | mkdir build && cd build 286 | cmake -DBUILD_STATIC=OFF -DWITH_EXAMPLES=ON .. 287 | cmake --build . 288 | sudo cmake --install . 289 | ``` 290 | 291 | ### Using qmake 292 | 293 | ```bash 294 | qmake QtSsh.pro 295 | make 296 | ``` 297 | 298 | See [Installation Guide](doc/installation.md) for detailed build instructions. 299 | 300 | ## Getting Help 301 | 302 | 1. Check the [Quick Start Guide](doc/quickstart.md) 303 | 2. Read the [Error Handling Guide](doc/errors.md) 304 | 3. Review example applications in `test/` directory 305 | 4. Consult API reference documentation in `doc/` 306 | 5. Check libssh2 documentation for underlying SSH functionality 307 | 308 | ## Features Comparison 309 | 310 | | Feature | QtSsh | Native libssh2 | 311 | |---------|-------|----------------| 312 | | Qt Integration | ✓ | ✗ | 313 | | Signals/Slots | ✓ | ✗ | 314 | | Channel Management | ✓ | Manual | 315 | | Progress Reporting | ✓ | Manual | 316 | | Error Handling | Comprehensive | Error codes | 317 | | SFTP Operations | High-level API | Low-level API | 318 | | Tunneling | Managed | Manual | 319 | 320 | ## Security Notes 321 | 322 | - Always use key-based authentication in production 323 | - Set appropriate file permissions (600 for private keys) 324 | - Validate host keys using known_hosts file 325 | - Use encrypted connections only (SSH protocol ensures this) 326 | - Keep libssh2 updated for security patches 327 | - Don't hardcode credentials in source code 328 | - Use secure key storage mechanisms 329 | 330 | ## Performance Tips 331 | 332 | - Reuse channels for multiple operations 333 | - Use SCP for simple file transfers (faster than SFTP) 334 | - Use SFTP for complex file operations 335 | - Keep persistent connections for multiple operations 336 | - Close unused channels to free resources 337 | - Use appropriate buffer sizes for large transfers 338 | 339 | ## Roadmap 340 | 341 | Current stable version: 1.3.4 342 | 343 | For feature requests and issues, please use the project's issue tracker. 344 | 345 | --- 346 | 347 | **Quick Links:** 348 | - [Installation](doc/installation.md) | [Quick Start](doc/quickstart.md) | [API Reference](doc/sshclient.md) | [Examples](test/) | [License](LICENSE) 349 | -------------------------------------------------------------------------------- /qtssh/sshtunneldataconnector.cpp: -------------------------------------------------------------------------------- 1 | #include "sshtunneldataconnector.h" 2 | #include 3 | #include 4 | #include "sshclient.h" 5 | 6 | Q_LOGGING_CATEGORY(logxfer, "ssh.tunnel.transfer", QtWarningMsg) 7 | #define DEBUGCH qCDebug(logxfer) << m_name 8 | 9 | SshTunnelDataConnector::SshTunnelDataConnector(SshClient *client, const QString &name, QObject *parent) 10 | : QObject(parent) 11 | , m_sshClient(client) 12 | , m_name(name) 13 | { 14 | DEBUGCH << "SshTunnelDataConnector constructor"; 15 | m_tx_data_on_sock = true; 16 | } 17 | 18 | SshTunnelDataConnector::~SshTunnelDataConnector() 19 | { 20 | emit processed(); 21 | if(m_sock) QObject::disconnect(m_sock); 22 | DEBUGCH << "TOTAL TRANSFERED: Tx:" << m_total_TxToSsh << " | Rx:" << m_total_RxToSock; 23 | } 24 | 25 | void SshTunnelDataConnector::setChannel(LIBSSH2_CHANNEL *channel) 26 | { 27 | m_sshChannel = channel; 28 | } 29 | 30 | void SshTunnelDataConnector::setSock(QTcpSocket *sock) 31 | { 32 | m_sock = sock; 33 | QObject::connect(m_sock, &QTcpSocket::disconnected, 34 | this, &SshTunnelDataConnector::_socketDisconnected); 35 | 36 | QObject::connect(m_sock, &QTcpSocket::destroyed, [this](){m_sock = nullptr;}); 37 | 38 | QObject::connect(m_sock, &QTcpSocket::readyRead, 39 | this, &SshTunnelDataConnector::_socketDataRecived); 40 | 41 | 42 | #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) 43 | QObject::connect(m_sock, &QAbstractSocket::errorOccurred, 44 | this, &SshTunnelDataConnector::_socketError); 45 | #else 46 | QObject::connect(m_sock, QOverload::of(&QAbstractSocket::error), 47 | this, &SshTunnelDataConnector::_socketError); 48 | #endif 49 | 50 | QObject::connect(m_sock, &QTcpSocket::bytesWritten, this, [this](qint64 len){ m_total_RxToSock += len; }); 51 | } 52 | 53 | void SshTunnelDataConnector::_socketDisconnected() 54 | { 55 | DEBUGCH << "_socketDisconnected: Socket disconnected"; 56 | m_tx_eof = true; 57 | emit processed(); 58 | emit sendEvent(); 59 | } 60 | 61 | void SshTunnelDataConnector::_socketDataRecived() 62 | { 63 | DEBUGCH << "_socketDataRecived: Socket data received"; 64 | m_tx_data_on_sock = true; 65 | emit sendEvent(); 66 | } 67 | 68 | void SshTunnelDataConnector::_socketError() 69 | { 70 | DEBUGCH << "_socketError"; 71 | emit processed(); 72 | auto error = m_sock->error(); 73 | switch(error) 74 | { 75 | case QAbstractSocket::RemoteHostClosedError: 76 | DEBUGCH << "socket RemoteHostClosedError, data available:" << m_sock->bytesAvailable(); 77 | // Socket will be closed just after this, nothing to care about 78 | break; 79 | default: 80 | qCWarning(logxfer) << m_name << "socket error=" << error << m_sock->errorString(); 81 | // setChannelState(ChannelState::Close); 82 | } 83 | } 84 | 85 | size_t SshTunnelDataConnector::_txBufferLen() const 86 | { 87 | if(m_tx_stop_ptr == nullptr || m_tx_start_ptr == nullptr) 88 | return 0; 89 | 90 | if(m_tx_stop_ptr < m_tx_start_ptr) 91 | { 92 | qCWarning(logxfer) << m_name << "TX Buffer error"; 93 | return 0; 94 | } 95 | 96 | return static_cast(m_tx_stop_ptr - m_tx_start_ptr); 97 | } 98 | 99 | ssize_t SshTunnelDataConnector::_transferSockToTx() 100 | { 101 | if(!m_sock) return 0; 102 | qint64 len = 0; 103 | if(_txBufferLen() != 0) 104 | { 105 | qCDebug(logxfer) << m_name << "Asking transfer sock to tx when buffer not empty (" << _txBufferLen() << " bytes)"; 106 | return -1; 107 | } 108 | 109 | if(m_sock == nullptr) 110 | { 111 | qCCritical(logxfer) << m_name << "_transferSockToTx on invalid socket"; 112 | return -1; 113 | } 114 | 115 | len = m_sock->read(m_tx_buffer, BUFFER_SIZE); 116 | m_tx_data_on_sock = (m_sock->bytesAvailable() > 0); 117 | if(len > 0) 118 | { 119 | m_tx_start_ptr = m_tx_buffer; 120 | m_tx_stop_ptr = m_tx_buffer + len; 121 | m_total_sockToTx += len; 122 | 123 | DEBUGCH << "_transferSockToTx: " << len << "bytes (available:" << m_sock->bytesAvailable() << ", state:" << m_sock->state() << ")"; 124 | if(m_tx_data_on_sock) 125 | { 126 | DEBUGCH << "_transferSockToTx: There is other data in socket, re-arm read"; 127 | emit sendEvent(); 128 | } 129 | } 130 | else if(len < 0) 131 | { 132 | qCWarning(logxfer) << m_name << "_transferSockToTx: error: " << len << " Bytes available " << m_sock->bytesAvailable(); 133 | m_tx_stop_ptr = nullptr; 134 | m_tx_start_ptr = nullptr; 135 | } 136 | else 137 | { 138 | DEBUGCH << m_name << "_transferSockToTx: 0 lenght"; 139 | m_tx_stop_ptr = nullptr; 140 | m_tx_start_ptr = nullptr; 141 | } 142 | 143 | emit processed(); 144 | if(len > 0) 145 | emit sendEvent(); 146 | return len; 147 | } 148 | 149 | ssize_t SshTunnelDataConnector::_transferTxToSsh() 150 | { 151 | ssize_t transfered = 0; 152 | 153 | if(m_tx_closed) return 0; 154 | if(!m_sshChannel) return 0; 155 | 156 | while(_txBufferLen() > 0) 157 | { 158 | ssize_t len = libssh2_channel_write(m_sshChannel, m_tx_start_ptr, _txBufferLen()); 159 | if(len == LIBSSH2_ERROR_EAGAIN) 160 | { 161 | return LIBSSH2_ERROR_EAGAIN; 162 | } 163 | if (len < 0) 164 | { 165 | char *emsg; 166 | int size; 167 | int ret = libssh2_session_last_error(m_sshClient->session(), &emsg, &size, 0); 168 | qCCritical(logxfer) << m_name << "Error" << ret << "libssh2_channel_write" << QString(emsg); 169 | return ret; 170 | } 171 | if (len == 0) 172 | { 173 | qCWarning(logxfer) << m_name << "ERROR: libssh2_channel_write return 0"; 174 | return 0; 175 | } 176 | /* xfer OK */ 177 | 178 | m_total_TxToSsh += len; 179 | m_tx_start_ptr += len; 180 | transfered += len; 181 | DEBUGCH << "_transferTxToSsh: write on SSH return " << len << "bytes" ; 182 | 183 | if(m_tx_start_ptr == m_tx_stop_ptr) 184 | { 185 | DEBUGCH << "_transferTxToSsh: All buffer sent on SSH, buffer empty" ; 186 | m_tx_stop_ptr = nullptr; 187 | m_tx_start_ptr = nullptr; 188 | } 189 | } 190 | 191 | emit processed(); 192 | return transfered; 193 | } 194 | 195 | size_t SshTunnelDataConnector::_rxBufferLen() const 196 | { 197 | if(m_rx_stop_ptr == nullptr || m_rx_start_ptr == nullptr) 198 | return 0; 199 | 200 | if(m_rx_stop_ptr < m_rx_start_ptr) 201 | { 202 | qCWarning(logxfer) << m_name << "RX Buffer error"; 203 | return 0; 204 | } 205 | 206 | return static_cast(m_rx_stop_ptr - m_rx_start_ptr); 207 | } 208 | 209 | ssize_t SshTunnelDataConnector::_transferSshToRx() 210 | { 211 | if(!m_sshChannel) return 0; 212 | 213 | if(_rxBufferLen() != 0) 214 | { 215 | qCDebug(logxfer) << "Buffer not empty, need to retry later"; 216 | emit sendEvent(); 217 | return 0; 218 | } 219 | 220 | ssize_t len = libssh2_channel_read(m_sshChannel, m_rx_buffer, BUFFER_SIZE); 221 | if(len == LIBSSH2_ERROR_EAGAIN) 222 | return 0; 223 | 224 | if (len < 0) 225 | { 226 | qCWarning(logxfer) << m_name << "_transferSshToRx: error: " << len; 227 | m_rx_stop_ptr = nullptr; 228 | m_rx_start_ptr = nullptr; 229 | 230 | char *emsg; 231 | int size; 232 | int ret = libssh2_session_last_error(m_sshClient->session(), &emsg, &size, 0); 233 | qCCritical(logxfer) << m_name << "Error" << ret << QString("libssh2_channel_read (%1 / %2)").arg(len).arg(BUFFER_SIZE) << QString(emsg); 234 | return 0; 235 | } 236 | 237 | m_total_SshToRx += len; 238 | 239 | if(len < BUFFER_SIZE) 240 | { 241 | DEBUGCH << "_transferSshToRx: Xfer " << len << "bytes"; 242 | m_rx_data_on_ssh = false; 243 | if (libssh2_channel_eof(m_sshChannel)) 244 | { 245 | m_rx_eof = true; 246 | DEBUGCH << "_transferSshToRx: Ssh channel closed"; 247 | } 248 | } 249 | else 250 | { 251 | DEBUGCH << "_transferSshToRx: Xfer " << len << "bytes; There is probably more data to read, re-arm event"; 252 | emit sendEvent(); 253 | } 254 | m_rx_stop_ptr = m_rx_buffer + len; 255 | m_rx_start_ptr = m_rx_buffer; 256 | emit processed(); 257 | return len; 258 | } 259 | 260 | ssize_t SshTunnelDataConnector::_transferRxToSock() 261 | { 262 | if(!m_sock) return 0; 263 | ssize_t total = 0; 264 | 265 | /* If socket not ready, wait for socket connected */ 266 | if(!m_sock || m_sock->state() != QAbstractSocket::ConnectedState) 267 | { 268 | qCDebug(logxfer) << m_name << "_transferRxToSock: Data on SSH when socket closed"; 269 | return -1; 270 | } 271 | 272 | if(m_rx_stop_ptr == nullptr) 273 | { 274 | qCWarning(logxfer) << m_name << "Buffer empty"; 275 | return 0; 276 | } 277 | 278 | DEBUGCH << "_transferRxToSock: Buffer contains " << _rxBufferLen() << "bytes"; 279 | 280 | while (_rxBufferLen() > 0) 281 | { 282 | ssize_t slen = m_sock->write(m_rx_start_ptr, m_rx_stop_ptr - m_rx_start_ptr); 283 | if (slen <= 0) 284 | { 285 | qCWarning(logxfer) << "ERROR : " << m_name << " local failed to write (" << slen << ")"; 286 | return slen; 287 | } 288 | 289 | m_rx_start_ptr += slen; 290 | total += slen; 291 | DEBUGCH << "_transferRxToSock: " << slen << "bytes written on socket"; 292 | } 293 | 294 | /* Buffer is empty */ 295 | m_rx_stop_ptr = nullptr; 296 | m_rx_start_ptr = nullptr; 297 | 298 | emit processed(); 299 | return total; 300 | } 301 | 302 | void SshTunnelDataConnector::sshDataReceived() 303 | { 304 | m_rx_data_on_ssh = true; 305 | } 306 | 307 | bool SshTunnelDataConnector::process() 308 | { 309 | if(!m_rx_closed) 310 | { 311 | if(m_rx_data_on_ssh) 312 | _transferSshToRx(); 313 | 314 | if(_rxBufferLen() || m_rx_eof) 315 | _transferRxToSock(); 316 | } 317 | 318 | if(!m_tx_closed) 319 | { 320 | if(m_tx_data_on_sock) 321 | _transferSockToTx(); 322 | 323 | if(_txBufferLen() || m_tx_eof) 324 | _transferTxToSsh(); 325 | } 326 | 327 | 328 | 329 | if(!m_tx_closed && m_tx_eof && (m_sock->bytesAvailable() == 0) && (_txBufferLen() == 0)) 330 | { 331 | DEBUGCH << "Send EOF to SSH"; 332 | int ret = libssh2_channel_send_eof(m_sshChannel); 333 | if(ret == 0) 334 | { 335 | m_tx_closed = true; 336 | emit sendEvent(); 337 | } 338 | } 339 | 340 | if(!m_rx_closed && m_rx_eof && (_rxBufferLen() == 0) && (m_sock->bytesAvailable() == 0) && (_txBufferLen() == 0)) 341 | { 342 | if(m_sock->state() == QAbstractSocket::ConnectedState) 343 | { 344 | DEBUGCH << "_transferRxToSock: RX EOF, need to close ???"; 345 | m_sock->disconnectFromHost(); 346 | } 347 | else 348 | { 349 | DEBUGCH << "_transferRxToSock: RX EOF, RX closed"; 350 | m_rx_closed = true; 351 | emit sendEvent(); 352 | } 353 | } 354 | 355 | DEBUGCH << "XFer Tx: Sock->" << m_total_sockToTx << "->Tx->" << m_total_TxToSsh << "->SSH" << ((m_tx_eof)?(" (EOF)"):("")) << ((m_tx_closed)?(" (CLOSED)"):("")); 356 | DEBUGCH << "XFer Rx: SSH->" << m_total_SshToRx << "->Rx->" << m_total_RxToSock << "->Sock(" << m_sock->state() << ")" << ((m_rx_eof)?(" (EOF)"):(""))<< ((m_rx_closed)?(" (CLOSED)"):("")); 357 | 358 | return (!m_rx_closed && !m_tx_closed); 359 | } 360 | 361 | void SshTunnelDataConnector::close() 362 | { 363 | if(m_sock && m_sock->state() == QAbstractSocket::ConnectedState) 364 | { 365 | m_sock->disconnectFromHost(); 366 | } 367 | else 368 | { 369 | m_rx_closed = true; 370 | } 371 | } 372 | 373 | bool SshTunnelDataConnector::isClosed() 374 | { 375 | DEBUGCH << "SshTunnelDataConnector::isClosed(tx:" << m_tx_closed << ", rx:" << m_rx_closed << ")"; 376 | return m_tx_closed && m_rx_closed && _rxBufferLen() == 0 && _txBufferLen() == 0; 377 | } 378 | 379 | void SshTunnelDataConnector::flushTx() 380 | { 381 | DEBUGCH << "flushTx: start " << ": Sock: " << m_sock->bytesAvailable() << " | buffer:" << _txBufferLen(); 382 | while(1) 383 | { 384 | if(m_sock->bytesAvailable() == 0 && _txBufferLen() == 0) 385 | break; 386 | 387 | if(m_tx_closed || m_rx_closed) 388 | break; 389 | 390 | if(m_sock->bytesAvailable() > 0) 391 | { 392 | _transferSockToTx(); 393 | } 394 | 395 | if(_txBufferLen() > 0) 396 | { 397 | if(_transferTxToSsh() == LIBSSH2_ERROR_EAGAIN) 398 | { 399 | QEventLoop wait(this); 400 | QObject::connect(this, &SshTunnelDataConnector::processed, &wait, &QEventLoop::quit); 401 | wait.exec(); 402 | } 403 | } 404 | } 405 | 406 | DEBUGCH << "flushTx: end " << ": Sock: " << m_sock->bytesAvailable() << " | buffer:" << _txBufferLen(); 407 | } 408 | --------------------------------------------------------------------------------