added class to open ssh connection using libssh and write data to ssh channel

This commit is contained in:
vladimir.kuznetsov 2022-12-20 13:43:46 +03:00
parent e481bd4ec5
commit c8085a368f
7 changed files with 271 additions and 137 deletions

View file

@ -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)

View file

@ -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));

View file

@ -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
View 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
View 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
View 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
View 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