added class to open ssh connection using libssh and write data to ssh channel
This commit is contained in:
parent
e481bd4ec5
commit
c8085a368f
7 changed files with 271 additions and 137 deletions
|
@ -21,13 +21,13 @@ set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen")
|
|||
find_package(Qt6 REQUIRED COMPONENTS
|
||||
Widgets Core Gui Network Xml
|
||||
RemoteObjects Quick Svg QuickControls2
|
||||
Core5Compat
|
||||
Core5Compat Concurrent
|
||||
)
|
||||
set(LIBS ${LIBS}
|
||||
Qt6::Widgets Qt6::Core Qt6::Gui
|
||||
Qt6::Network Qt6::Xml Qt6::RemoteObjects
|
||||
Qt6::Quick Qt6::Svg Qt6::QuickControls2
|
||||
Qt6::Core5Compat
|
||||
Qt6::Core5Compat Qt6::Concurrent
|
||||
)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
@ -71,6 +71,8 @@ set(HEADERS ${HEADERS}
|
|||
${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshsession.h
|
||||
)
|
||||
|
||||
if(NOT IOS)
|
||||
|
@ -93,6 +95,8 @@ set(SOURCES ${SOURCES}
|
|||
${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshsession.cpp
|
||||
)
|
||||
|
||||
if(NOT IOS)
|
||||
|
|
|
@ -43,12 +43,10 @@ using namespace QSsh;
|
|||
ServerController::ServerController(std::shared_ptr<Settings> settings, QObject *parent) :
|
||||
m_settings(settings)
|
||||
{
|
||||
ssh_init();
|
||||
}
|
||||
|
||||
ServerController::~ServerController()
|
||||
{
|
||||
ssh_finalize();
|
||||
}
|
||||
|
||||
ErrorCode ServerController::connectToHost(const ServerCredentials &credentials, ssh_session &session) {
|
||||
|
@ -58,14 +56,14 @@ ErrorCode ServerController::connectToHost(const ServerCredentials &credentials,
|
|||
}
|
||||
|
||||
int port = credentials.port;
|
||||
int log_verbosity = SSH_LOG_NOLOG;
|
||||
std::string host_ip = credentials.hostName.toStdString();
|
||||
std::string host_username = credentials.userName.toStdString() + "@" + host_ip;
|
||||
int logVerbosity = SSH_LOG_NOLOG;
|
||||
std::string hostIp = credentials.hostName.toStdString();
|
||||
std::string hostUsername = credentials.userName.toStdString() + "@" + hostIp;
|
||||
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, host_ip.c_str());
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, hostIp.c_str());
|
||||
ssh_options_set(session, SSH_OPTIONS_PORT, &port);
|
||||
ssh_options_set(session, SSH_OPTIONS_USER, host_username.c_str());
|
||||
ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &log_verbosity);
|
||||
ssh_options_set(session, SSH_OPTIONS_USER, hostUsername.c_str());
|
||||
ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &logVerbosity);
|
||||
|
||||
int connection_result = ssh_connect(session);
|
||||
|
||||
|
@ -82,7 +80,7 @@ ErrorCode ServerController::connectToHost(const ServerCredentials &credentials,
|
|||
auth_result = ssh_userauth_publickey(session, auth_username.c_str(), priv_key);
|
||||
}
|
||||
else {
|
||||
auth_result = ssh_userauth_password(session, auth_username.c_str(), credentials.password.toStdString().c_str());
|
||||
auth_result = ssh_userauth_password(session, auth_username.c_str(), credentials.password.toStdString().c_str());
|
||||
}
|
||||
|
||||
if (auth_result != SSH_OK) {
|
||||
|
@ -98,67 +96,38 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
|
|||
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdOut,
|
||||
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr) {
|
||||
|
||||
ssh_session ssh = ssh_new();
|
||||
|
||||
ErrorCode e = connectToHost(credentials, ssh);
|
||||
if (e) {
|
||||
ssh_free(ssh);
|
||||
return e;
|
||||
std::shared_ptr<SshSession> session = m_sshClient.getSession();
|
||||
if (!session) {
|
||||
return ErrorCode::SshInternalError;
|
||||
}
|
||||
ssh_channel channel = ssh_channel_new(ssh);
|
||||
|
||||
if (channel == NULL) {
|
||||
ssh_disconnect(ssh);
|
||||
ssh_free(ssh);
|
||||
return ErrorCode::SshAuthenticationError;
|
||||
}
|
||||
|
||||
ssh_channel_open_session(channel);
|
||||
|
||||
if (ssh_channel_is_open(channel)) {
|
||||
qDebug() << "SSH chanel opened";
|
||||
} else {
|
||||
ssh_channel_free(channel);
|
||||
ssh_disconnect(ssh);
|
||||
ssh_free(ssh);
|
||||
return ErrorCode::SshAuthenticationError;
|
||||
auto error = session->initChannel(credentials);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
script.replace("\r", "");
|
||||
|
||||
qDebug() << "Run script " << script;
|
||||
|
||||
int exec_result = ssh_channel_request_pty(channel);
|
||||
|
||||
ssh_channel_change_pty_size(channel, 80, 1024);
|
||||
ssh_channel_request_shell(channel);
|
||||
|
||||
QString totalLine;
|
||||
const QStringList &lines = script.split("\n", Qt::SkipEmptyParts);
|
||||
for (int i = 0; i < lines.count(); i++) {
|
||||
QString currentLine = lines.at(i);
|
||||
QString nextLine;
|
||||
if (i + 1 < lines.count()) nextLine = lines.at(i+1);
|
||||
|
||||
if (totalLine.isEmpty()) {
|
||||
totalLine = currentLine;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
totalLine = totalLine + "\n" + currentLine;
|
||||
}
|
||||
|
||||
QString lineToExec;
|
||||
if (currentLine.endsWith("\\")) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
lineToExec = totalLine;
|
||||
totalLine.clear();
|
||||
}
|
||||
|
||||
qDebug()<<"total line " << totalLine;
|
||||
|
||||
// Run collected line
|
||||
if (lineToExec.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
@ -167,43 +136,14 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
|
|||
|
||||
qDebug().noquote() << "EXEC" << lineToExec;
|
||||
Debug::appendSshLog("Run command:" + lineToExec);
|
||||
int nwritten, nbytes = 0, tryes = 0;
|
||||
char buffer[2048];
|
||||
if (ssh_channel_write(channel, lineToExec.toUtf8(), (uint32_t)lineToExec.size()) == lineToExec.size() &&
|
||||
ssh_channel_write(channel, "\n", 1) == 1){
|
||||
while (nbytes !=0 || tryes < 100){
|
||||
if (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) {
|
||||
//nbytes = ssh_channel_read_nonblocking(channel, buffer, sizeof(buffer), 0);
|
||||
|
||||
nbytes = ssh_channel_read_timeout(channel, buffer, sizeof(buffer), 0, 50);
|
||||
|
||||
if (nbytes > 0) {
|
||||
tryes = 0;
|
||||
std::string strbuf;
|
||||
strbuf.assign(buffer, nbytes);
|
||||
QByteArray qbuff(buffer, nbytes);
|
||||
QString outp(qbuff);
|
||||
|
||||
if (cbReadStdOut){
|
||||
cbReadStdOut(outp, nullptr);
|
||||
}
|
||||
qDebug().noquote() << QString(strbuf.c_str());
|
||||
|
||||
} else {
|
||||
tryes++;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
||||
}
|
||||
error = session->writeToChannel(lineToExec);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
ssh_channel_send_eof(channel);
|
||||
ssh_channel_free(channel);
|
||||
ssh_disconnect(ssh);
|
||||
ssh_free(ssh);
|
||||
|
||||
qDebug() << "ServerController::runScript finished\n";
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
|
@ -214,7 +154,7 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
|
|||
const std::function<void (const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr)
|
||||
{
|
||||
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
||||
Debug::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script);
|
||||
Debug::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + QStringLiteral(":\n") + script);
|
||||
|
||||
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
|
||||
if (e) return e;
|
||||
|
@ -888,60 +828,6 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential
|
|||
return stdOut;
|
||||
}
|
||||
|
||||
SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams)
|
||||
{
|
||||
SshConnection *client = acquireConnection(sshParams);
|
||||
if (!client) return nullptr;
|
||||
|
||||
QEventLoop waitssh;
|
||||
QObject::connect(client, &SshConnection::connected, &waitssh, [&]() {
|
||||
qDebug() << "Server connected by ssh";
|
||||
waitssh.quit();
|
||||
});
|
||||
|
||||
QObject::connect(client, &SshConnection::disconnected, &waitssh, [&]() {
|
||||
qDebug() << "Server disconnected by ssh";
|
||||
waitssh.quit();
|
||||
});
|
||||
|
||||
QObject::connect(client, &SshConnection::error, &waitssh, [&](QSsh::SshError error) {
|
||||
qCritical() << "Ssh error:" << error << client->errorString();
|
||||
waitssh.quit();
|
||||
});
|
||||
|
||||
|
||||
// QObject::connect(client, &SshConnection::dataAvailable, [&](const QString &message) {
|
||||
// qCritical() << "Ssh message:" << message;
|
||||
// });
|
||||
|
||||
//qDebug() << "Connection state" << client->state();
|
||||
|
||||
if (client->state() == SshConnection::State::Unconnected) {
|
||||
client->connectToHost();
|
||||
waitssh.exec();
|
||||
}
|
||||
|
||||
|
||||
// QObject::connect(&client, &SshClient::sshDataReceived, [&](){
|
||||
// qDebug().noquote() << "Data received";
|
||||
// });
|
||||
|
||||
|
||||
// if(client.sshState() != SshClient::SshState::Ready) {
|
||||
// qCritical() << "Can't connect to server";
|
||||
// return false;
|
||||
// }
|
||||
// else {
|
||||
// qDebug() << "SSh connection established";
|
||||
// }
|
||||
|
||||
|
||||
// QObject::connect(proc, &SshProcess::finished, &wait, &QEventLoop::quit);
|
||||
// QObject::connect(proc, &SshProcess::failed, &wait, &QEventLoop::quit);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
void ServerController::disconnectFromHost(const ServerCredentials &credentials)
|
||||
{
|
||||
SshConnection *client = acquireConnection(sshParams(credentials));
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "containers/containers_defs.h"
|
||||
|
||||
#include "sftpdefs.h"
|
||||
#include "sshclient.h"
|
||||
|
||||
class Settings;
|
||||
class VpnConfigurator;
|
||||
|
@ -76,7 +77,6 @@ public:
|
|||
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject());
|
||||
|
||||
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr);
|
||||
QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams);
|
||||
|
||||
private:
|
||||
|
||||
|
@ -92,6 +92,8 @@ private:
|
|||
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
std::shared_ptr<VpnConfigurator> m_configurator;
|
||||
|
||||
SshClient m_sshClient;
|
||||
};
|
||||
|
||||
#endif // SERVERCONTROLLER_H
|
||||
|
|
19
client/core/sshclient.cpp
Normal file
19
client/core/sshclient.cpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include "sshclient.h"
|
||||
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
SshClient::SshClient(QObject *parent) : QObject(parent)
|
||||
{
|
||||
ssh_init();
|
||||
}
|
||||
|
||||
SshClient::~SshClient()
|
||||
{
|
||||
ssh_finalize();
|
||||
}
|
||||
|
||||
std::shared_ptr<SshSession> SshClient::getSession()
|
||||
{
|
||||
return std::make_shared<SshSession>();
|
||||
}
|
20
client/core/sshclient.h
Normal file
20
client/core/sshclient.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#ifndef SSHCLIENT_H
|
||||
#define SSHCLIENT_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "sshsession.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class SshClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SshClient(QObject *parent = nullptr);
|
||||
~SshClient();
|
||||
|
||||
std::shared_ptr<SshSession> getSession();
|
||||
};
|
||||
|
||||
#endif // SSHCLIENT_H
|
167
client/core/sshsession.cpp
Normal file
167
client/core/sshsession.cpp
Normal file
|
@ -0,0 +1,167 @@
|
|||
#include "sshsession.h"
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QtConcurrent>
|
||||
|
||||
SshSession::SshSession(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SshSession::~SshSession()
|
||||
{
|
||||
if (m_isNeedSendChannelEof) {
|
||||
ssh_channel_send_eof(m_channel);
|
||||
}
|
||||
if (m_isChannelOpened) {
|
||||
ssh_channel_free(m_channel);
|
||||
}
|
||||
if (m_isSessionConnected) {
|
||||
ssh_disconnect(m_session);
|
||||
}
|
||||
ssh_free(m_session);
|
||||
}
|
||||
|
||||
ErrorCode SshSession::connectToHost(const ServerCredentials &credentials)
|
||||
{
|
||||
if (m_session == NULL) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
int port = credentials.port;
|
||||
int logVerbosity = SSH_LOG_NOLOG;
|
||||
std::string hostIp = credentials.hostName.toStdString();
|
||||
std::string hostUsername = credentials.userName.toStdString() + "@" + hostIp;
|
||||
|
||||
ssh_options_set(m_session, SSH_OPTIONS_HOST, hostIp.c_str());
|
||||
ssh_options_set(m_session, SSH_OPTIONS_PORT, &port);
|
||||
ssh_options_set(m_session, SSH_OPTIONS_USER, hostUsername.c_str());
|
||||
ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &logVerbosity);
|
||||
|
||||
int connectionResult = ssh_connect(m_session);
|
||||
|
||||
if (connectionResult != SSH_OK) {
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return ErrorCode::SshTimeoutError;
|
||||
}
|
||||
|
||||
m_isSessionConnected = true;
|
||||
|
||||
std::string authUsername = credentials.userName.toStdString();
|
||||
|
||||
int authResult = SSH_ERROR;
|
||||
if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) {
|
||||
ssh_key privateKey;
|
||||
ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, nullptr, nullptr, &privateKey);
|
||||
authResult = ssh_userauth_publickey(m_session, authUsername.c_str(), privateKey);
|
||||
}
|
||||
else {
|
||||
authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.password.toStdString().c_str());
|
||||
}
|
||||
|
||||
if (authResult != SSH_OK) {
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return ErrorCode::SshAuthenticationError;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SshSession::initChannel(const ServerCredentials &credentials)
|
||||
{
|
||||
m_session = ssh_new();
|
||||
|
||||
ErrorCode error = connectToHost(credentials);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
m_channel = ssh_channel_new(m_session);
|
||||
|
||||
if (m_channel == NULL) {
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return ErrorCode::SshAuthenticationError;
|
||||
}
|
||||
|
||||
int result = ssh_channel_open_session(m_channel);
|
||||
|
||||
if (result == SSH_OK && ssh_channel_is_open(m_channel)) {
|
||||
qDebug() << "SSH chanel opened";
|
||||
} else {
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return ErrorCode::SshAuthenticationError;
|
||||
}
|
||||
|
||||
result = ssh_channel_request_pty(m_channel);
|
||||
if (result != SSH_OK) {
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return ErrorCode::SshInternalError;
|
||||
}
|
||||
|
||||
result = ssh_channel_change_pty_size(m_channel, 80, 1024);
|
||||
if (result != SSH_OK) {
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return ErrorCode::SshInternalError;
|
||||
}
|
||||
|
||||
result = ssh_channel_request_shell(m_channel);
|
||||
if (result != SSH_OK) {
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return ErrorCode::SshInternalError;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SshSession::writeToChannel(const QString &data)
|
||||
{
|
||||
QFutureWatcher<ErrorCode> watcher;
|
||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &SshSession::writeToChannelFinished);
|
||||
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([this, &data]() {
|
||||
return write(data);
|
||||
});
|
||||
watcher.setFuture(future);
|
||||
|
||||
QEventLoop wait;
|
||||
|
||||
QObject::connect(this, &SshSession::writeToChannelFinished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
return watcher.result();
|
||||
}
|
||||
|
||||
ErrorCode SshSession::write(const QString &data)
|
||||
{
|
||||
const int channelReadTimeoutMs = 10;
|
||||
const size_t bufferSize = 2048;
|
||||
|
||||
int bytesToRead = 0;
|
||||
int attempts = 0;
|
||||
char buffer[bufferSize];
|
||||
|
||||
int bytesWritten = ssh_channel_write(m_channel, data.toUtf8(), (uint32_t)data.size());
|
||||
if (bytesWritten == data.size()) {
|
||||
while (bytesToRead != 0 || attempts < 100){
|
||||
if (ssh_channel_is_open(m_channel) && !ssh_channel_is_eof(m_channel)) {
|
||||
bytesToRead = ssh_channel_read_timeout(m_channel, buffer, sizeof(buffer), 0, channelReadTimeoutMs);
|
||||
if (bytesToRead > 0) {
|
||||
attempts = 0;
|
||||
std::string strbuf(buffer, bytesToRead);
|
||||
// QByteArray qbuff(buffer, bytesToRead);
|
||||
// QString outp(buffer);
|
||||
|
||||
// if (cbReadStdOut){
|
||||
// cbReadStdOut(outp, nullptr);
|
||||
// }
|
||||
qDebug().noquote() << QString(strbuf.c_str());
|
||||
} else {
|
||||
attempts++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return ErrorCode::SshInternalError;
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
}
|
36
client/core/sshsession.h
Normal file
36
client/core/sshsession.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef SSHSESSION_H
|
||||
#define SSHSESSION_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
#include "defs.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class SshSession : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SshSession(QObject *parent = nullptr);
|
||||
~SshSession();
|
||||
|
||||
ErrorCode initChannel(const ServerCredentials &credentials);
|
||||
ErrorCode writeToChannel(const QString &data);
|
||||
private:
|
||||
ErrorCode connectToHost(const ServerCredentials &credentials);
|
||||
ErrorCode write(const QString &data);
|
||||
|
||||
ssh_session m_session;
|
||||
ssh_channel m_channel;
|
||||
|
||||
bool m_isChannelOpened = false;
|
||||
bool m_isSessionConnected = false;
|
||||
bool m_isNeedSendChannelEof = false;
|
||||
signals:
|
||||
void writeToChannelFinished();
|
||||
};
|
||||
|
||||
#endif // SSHSESSION_H
|
Loading…
Add table
Add a link
Reference in a new issue