Qtの基礎 - SSH
概要
Qtにおいて、簡潔にSSH接続を確立する手順を記載する。
- libSSHライブラリ (LGPLライセンス) を使用する。
- libSSH2ライブラリ (3条項BSDライセンス) を使用する。
QProcess
クラスを使用して、外部のSSHプロセスをフォークして接続を確立する。- wolfSSH
- wolfSSHライブラリは、wolfSSLと連携して使用する。
- 無償版は、GPL v2ライセンスである。
- 有償版は、独自ライセンスである。
- https://wolfssl.jp/products/wolfssh/
- Github : https://github.com/wolfSSL/wolfssh
- wolfSSLのGithub : https://github.com/wolfSSL/wolfssl
- また、wolfSSLは、IoTデバイス、組み込み向け小型デバイスにも対応した軽量のSSL/TLSライブラリである。
- QSshライブラリを使用する。
- QSshは、QtアプリケーションにSSHとSFTPのサポートを提供する。
- QSshプロジェクトは、過去のQt CreatorのSSHプラグインに基づいており、全てのクレジットはQt Creatorチームに帰属する。
- ただし、現在では、Qt CreatorはOpenSSHを使用している。
- https://github.com/sandsmark/QSsh
libSSHとlibSSH2の比較
libSSHとlibSSH2は、どちらもSSHプロトコルを実装したライブラリである。
libSSHはC言語、libSSH2はC++言語で記述されている。
また、libSSHは単独のライブラリとして提供されているのに対して、libSSH2はlibSSL等の他のライブラリと組み合わせて使用することが多い。
プロジェクトの起源と開発者
- libSSH
- libSSHは、フランスのBenjamin GilbertとArel Corderoによって開発された。
- libSSHは、SSHプロトコルの実装を提供しており、クライアントおよびサーバの機能をサポートしている。
- libSSH2
- libSSH2は、curlライブラリの主要な開発者でもあるスウェーデンのDaniel Stenbergによって開発された。
- libSSH2は、SSHのクライアントおよびサーバ機能を提供することを目的としている。
- ただし、libSSH2はサーバサイドのサポートは行っていない。
使用されるコードベース
- libSSH
- libSSHは、元々PuTTYの一部として開発され、その後独立したプロジェクトになった。
- PuTTYは、Windows環境でのSSHクライアントとして広く知られている。
- libSSH2
- libSSH2は、curlライブラリとは独立したプロジェクトとして開発されている。
項目 | libSSH2 | libSSH |
---|---|---|
ライブラリ名 | libssh2.so | libssh.so |
ライセンス | 3条項BSD | LGPL 2.1 |
サーバサイドのサポート | No | Yes |
GSSAPI認証 | No | Yes |
楕円曲線鍵の交換 | No | Yes |
楕円曲線鍵のホスト鍵 | No | Yes |
ナイトリーテストによるテストケースの自動化 | No | Yes |
Stable API | Yes | ほとんどの部分 |
C言語との互換 | C89 | C99 |
厳格な名前空間 | Yes | Yes |
全ての関数のマニュアル | Yes | No |
全ての関数のDoxygenドキュメント | No | Yes |
Tutorial | Yes | Yes |
SSH v1のサポート | No | Yes |
libSSHライブラリのインストール
libSSHとは
libSSHの多くは、LGPL 2.1ライセンスである。
一部の機能には、2条項BSDライセンスのものがある。
パッケージ管理システムからインストール
sudo zypper install libssh-devel
ソースコードからインストール
libSSHのビルドに必要なライブラリをインストールする。
sudo zypper install zlib-devel readline-devel libpcap-devel libopenssl-devel libopenssl-1_1-devel libgcrypt-devel p11-kit-devel libsodium-devel libcmocka-devel doxygen \ # 以下に示すライブラリは不要の可能性あり openpgm-devel ldns-devel zeromq-devel unbound-devel libunwind-devel \ libheimdal-devel libgssglue-devel gssntlmssp-devel
libSSHの公式WebサイトまたはlibSSH向けのGitにアクセスして、libSSHのソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。
tar xf libssh-<バージョン>.tar.xz cd libssh-<バージョン>
libSSHをビルドおよびインストールする。
mkdir build && cd build cmake .. \ -DCMAKE_INSTALL_PREFIX=/<libSSHのインストールディレクトリ> -DCMAKE_BUILD_TYPE=Release \ -DWITH_GSSAPI=ON -DWITH_DSA=ON -DWITH_GCRYPT=ON \ # オプション -DWITH_PKCS11_URI=ON -DWITH_BLOWFISH_CIPHER=ON \ # オプション -DCMAKE_C_COMPILER=/<GCCのインストールディレクトリ>/bin/gcc \ # オプション -DCMAKE_CXX_COMPILER=/<GCCのインストールディレクトリ>/bin/g++ # オプション make -j $(nproc) make install
libSSHの使用例は、以下に示すURLを参考にすること。
https://api.libssh.org/stable/libssh_tutorial.html
libSSH2ライブラリのインストール
libSSH2とは
libSSH2は、3条項BSDライセンスである。(4条項BSDライセンスから、3番目にあった「宣伝条項」を削除したもの)
これにより、GPLと互換性が生まれたため、BSDライセンスのソフトウェアをGPLで配布することができる。(その逆は不可)
パッケージ管理システムからインストール
sudo zypper install libssh2-devel
ソースコードからインストール
libSSH2のビルドに必要なライブラリをインストールする。
sudo zypper install zlib-devel libopenssl-devel libopenssl-1_1-devel
libSSH2の公式WebサイトまたはGithubにアクセスして、libSSH2のソースコードをダウンロードする。
ダウンロードしたソースコードを解凍する。
tar xf libssh2.tar.xz cd libssh2
libSSH2をビルドおよびインストールする。
mkdir build && cd build # configureスクリプトを使用する場合 ../cofigur --prefix=<libSSH2のインストールディレクトリ> \ --enable-examples-build --disable-debug --with-libz make -j $(nproc) make install # CMakeを使用する場合 cmake .. \ -DCMAKE_INSTALL_PREFIX=/<libssh2のインストールディレクトリ> \ -DLINT=ON -DBUILD_SHARED_LIBS=ON -DCLEAR_MEMORY=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_DEBUG_LOGGING=OFF \ -DCMAKE_C_COMPILER=/<GCCのインストールディレクトリ>/bin/gcc \ # オプション -DCMAKE_CXX_COMPILER=/<GCCのインストールディレクトリ>/bin/g++ # オプション make -j $(nproc) make install
libSSH2の使用例は、以下に示すURLを参考にすること。
http://www.chaosstuff.com/2013/09/gnome-mplayer-remote-with-qt-and-libssh2.html
https://bitbucket.org/nchokoev/qtsshremote
Windowsの場合、CMakeおよびVisual Studioを使用してlibSSH2をビルドおよびインストールすることができる。
- CMakeを使用する場合
- 32ビット Windows
cmake -DBUILD_SHARED_LIBS=ON -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF -A Win32 .. -B "x86"
cmake --build x86 --config Release
- 64ビット Windows
cmake -DBUILD_SHARED_LIBS=ON -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF -A x64 .. -B "x64"
cmake --build x64 --config Release
- コンパイルされたDLLファイルとlibファイルは、x86とx64のディレクトリのsrc/Releaseディレクトリに配置される。
- 32ビット Windows
- Visual Studioを使用する場合
- libSSH2ディレクトリと同階層にプロジェクトを作成する。
- プロジェクトのプロパティを選択して、プロパティ画面左ペインにある[General] - プロパティ画面右ペインにある[ターゲット名]を"libSSH2_x64"等と入力する。
- プロパティ画面左ペインにある[リンカ] - [Advanced] - プロパティ画面右ペインにある[インポートライブラリ]プルダウンから[<Inherit from parent or project defaults>]を選択する。
また、ビルドされたDLLファイルは、以下に示すWebサイトからダウンロードできる。
https://download.csdn.net/download/sdhongjun/15682389
libSSHの使用例
以下の例では、公開鍵認証を使用してリモートPCへSSH接続している。
# .pro プロジェクトファイル
# pkg-configを使用する場合
CONFIG += link_pkgconfig
LIBS += \
-L/<libSSHのインストールディレクトリ>/lib64 -lssh
# pkg-configを使用しない場合
LIBS += \
-L/<libSSHのインストールディレクトリ>/lib64 -lssh
INCLUDEPATH += \
/<libSSHのインストールディレクトリ>/include
#include <QCoreApplication>
#include <QMessageBox>
#include <libssh/libssh.h>
int VerifyKnownsHost(ssh_session my_ssh_session, QString &strErrMsg);
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// SSHセッションの作成
ssh_session my_ssh_session = ssh_new();
if (my_ssh_session == NULL) {
// 作成に失敗した場合
return -1;
}
// SSHセッションの設定
QString host = "<リモートPCのIPアドレス または ホスト名>";
QString user = "<リモートPCのユーザ名>";
QString port = "<SSHポート番号 例: 22>";
ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, host.toUtf8().data());
ssh_options_set(my_ssh_session, SSH_OPTIONS_USER, user.toUtf8().data());
ssh_options_set(my_ssh_session, SSH_OPTIONS_PORT_STR, port.toUtf8().data());
// SSH接続
int rc = ssh_connect(my_ssh_session);
if (rc != SSH_OK) {
// 接続に失敗した場合
fprintf(stderr, "Error connecting to host: %s\n", ssh_get_error(my_ssh_session));
ssh_free(my_ssh_session);
return -1;
}
// ~/.sshディレクトリ等にあるファイルに記述されているサーバのIDを検証
QString strErrMsg = "";
if(VerifyKnownsHost(my_ssh_session, strErrMsg) < 0)
{
fprintf(stderr, "%s\n", strErrMsg.toUtf8().constData();
if(my_ssh_session != nullptr)
{
ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_session);
}
return -1;
}
// 公開鍵認証
// 秘密鍵の設定
const char *private_key_path = "<秘密鍵のパス 例: /home/user/sshkey/id_rsa";
// 秘密鍵のパスフレーズを設定していない場合
rc = ssh_userauth_privatekey_file(my_ssh_session, nullptr, private_key_path, nullptr);
// 秘密鍵のパスフレーズを設定している場合
rc = ssh_userauth_privatekey_file(my_ssh_session, nullptr, private_key_path, "<秘密鍵のパスフレーズ>");
if (rc != SSH_AUTH_SUCCESS) {
// 認証に失敗した場合
fprintf(stderr, "Error authenticating with private key: %s\n", ssh_get_error(my_ssh_session));
ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_session);
return -1;
}
// SSHセッションを使用して任意の処理を実行
// scpコマンドの実行、または、リモート先で任意のコマンドの実行等
// ...略
// SSH接続の切断
ssh_disconnect(my_ssh_session);
// SSHセッションの解放
ssh_free(my_ssh_session);
return 0;
}
int VerifyKnownsHost(ssh_session my_ssh_session, QString &strErrMsg)
{
// Authenticating the server.
ssh_key srv_pubkey = {};
if(ssh_get_server_publickey(my_ssh_session, &srv_pubkey) < 0)
{
strErrMsg = tr("Failed to get public key.");
return -1;
}
unsigned char *hash = nullptr;
size_t hlen = 0L;
auto iRet = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen);
ssh_key_free(srv_pubkey);
if(iRet < 0)
{
strErrMsg = tr("Failed to get public key hash.");
return -1;
}
auto state = ssh_session_is_known_server(my_ssh_session);
if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_OK)
{ // Authentication Successful
}
else if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_CHANGED)
{
QString strHexa = ssh_get_hexa(hash, hlen);
// print string in reverse order
strErrMsg = tr("Host key for server changed:") + "<br>" +
tr("For security reasons, connection will be stopped.") + "<br><br>" +
tr("Public key hash:") + "<br>" + strHexa + "<br>" + hlen;
ssh_clean_pubkey_hash(&hash);
return -1;
}
else if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_OTHER)
{
strErrMsg = tr("The host key for this server was not found but an other type of key exists.") + "<br>" +
tr("An attacker might change the default server key to confuse your client into") + "<br>" +
tr("thinking the key does not exist");
ssh_clean_pubkey_hash(&hash);
return -1;
}
else if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_NOT_FOUND)
{
/* FALL THROUGH to SSH_KNOWN_HOSTS_UNKNOWN behavior */
QString strHexa = ssh_get_hexa(hash, hlen);
QString strAddHostMessage = tr("Could not find known host file.") + "\n" +
tr("If you accept the host key here, the file will be automatically created.") + "\n\n" +
tr("The server is unknown. Do you trust the host key?") + "\n" +
tr("Public key hash: ") + "\n" + strHexa;
auto ret = QMessageBox(QMessageBox::Warning, QMessageBox::tr("Add Host"), strAddHostMessage,
QMessageBox::Yes | QMessageBox::No, nullptr).exec();
ssh_clean_pubkey_hash(&hash);
if(ret == QMessageBox::No)
{
strErrMsg = tr("To connect, please add host key.");
return -1;
}
else
{
iRet = ssh_session_update_known_hosts(my_ssh_session);
if(iRet < 0)
{
strErrMsg = tr("Failed to update host key.");
return -1;
}
}
}
else if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_UNKNOWN)
{
QString strHexa = ssh_get_hexa(hash, hlen);
QString strAddHostMessage = tr("The server is unknown. Do you trust the host key?") + "\n" +
tr("Public key hash: ") + "\n" + strHexa;
auto msgRet = QMessageBox(QMessageBox::Warning, QMessageBox::tr("Add Host"), strAddHostMessage,
QMessageBox::Yes | QMessageBox::No, nullptr).exec();
ssh_clean_pubkey_hash(&hash);
if(msgRet == QMessageBox::No)
{
strErrMsg = tr("To connect, please add host key.");
return -1;
}
else
{
iRet = ssh_session_update_known_hosts(my_ssh_session);
if(iRet < 0)
{
strErrMsg = tr("Failed to update host key.");
return -1;
}
}
}
else if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_ERROR)
{
strErrMsg = tr("There was an error in checking the host.");
ssh_clean_pubkey_hash(&hash);
return -1;
}
ssh_clean_pubkey_hash(&hash);
return 0;
}
libSSHを使用した独自クラスの使用
以下に示すクラスは、クロスプラットフォームの非同期SSHおよびSCPソケットである。
このクラスは、libSSHを必要とする。(RSA鍵の受け渡しを隠したり、コマンドの応答をreadyReadシグナル経由ではなく、シングルショットで送信している)
Windowsで動作させるには、このクラスを使用するファイルの最上部にインクルードする必要がある。
これにより、QtがWindowsソケットをインクルードする前にWindows.hファイルをインクルードさせないようにする。
ファイル:CSSH.zip
libSSH2ライブラリを使用したコマンドの使用例
ブロッキングモード
以下の例では、リモート側のPCにSSH接続して、lsコマンドを実行してその結果をローカル側のPCに表示している。
以下の例は、ブロッキングモードでlsコマンドを実行している。
# .pro プロジェクトファイル
# QTcpSocketを使用する場合
QT += network
# pkg-configを使用する場合
CONFIG += link_pkgconfig
LIBS += \
-L/<libSSH2のインストールディレクトリ>/lib64 -lssh2
# pkg-configを使用しない場合
LIBS += \
-L/<libSSH2のインストールディレクトリ>/lib64 -lssh2
INCLUDEPATH += \
/<libSSH2のインストールディレクトリ>/include
// main.cpp
#include <QCoreApplication>
#include <QTcpSocket>
#include <QDebug>
#include <libssh2.h>
#include "DivideByZeroException.h"
void DisConnect(QTcpSocket &sock, LIBSSH2_SESSION *session);
int ExecCommand(QTcpSocket &sock, LIBSSH2_SESSION *session);
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int rc = 0;
QTcpSocket sock;
LIBSSH2_SESSION *session = nullptr;
// libSSH2オブジェクトの初期化
auto rc = libssh2_init(0);
if (rc != 0) {
qDebug() << "libssh2 initialization failed";
DisConnect(sock, session);
return -1;
}
// QTcpSocketクラスのインスタンスを生成して、リモートPCに接続
sock.connectToHost(<リモートPCのIPアドレスまたはホスト名>, <リモートPCにSSH接続するポート番号>);
// 最大10[秒]待機
if (!sock.waitForConnected(10000)) {
qDebug() << "SSH接続に失敗:" << socket.errorString();
DisConnect(sock, session);
return -1;
}
// SSHのセッションの初期化
session = libssh2_session_init();
if (!session) {
qDebug() << "SSHのセッションの初期化に失敗";
DisConnect(sock, session);
return -1;
}
// SSHのハンドシェイク
if (libssh2_session_handshake(session, sock.socketDescriptor()) != 0) {
qDebug() << "SSHのハンドシェイクに失敗";
DisConnect(sock, session);
return -1;
}
// リモートPCのフィンガープリントを確認
auto fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
qDebug() << "リモートPCのフィンガープリント:" << fingerprint;
// 認証方法の確認
auto userauth_list = libssh2_userauth_list(session, "<リモートPCのユーザ名>", strlen(USERNAME));
qDebug() << "認証方法:" << userauth_list;
// パスワード認証を行う場合
//if (libssh2_userauth_password(session, "<リモートPCのユーザ名>", "<リモートPCのユーザパスワード>") != 0) {
// qDebug() << "パスワードによる認証に失敗";
// return -1;
//}
// 公開鍵認証
if (libssh2_userauth_publickey_fromfile(session, "<リモートPCのユーザ名>", "<公開鍵ファイルのパス>", "<秘密鍵ファイルのパス>", "<秘密鍵のパスフレーズ>") != 0) {
qDebug() << "公開鍵認証に失敗";
DisConnect(sock, session);
return -1;
}
qDebug() << QString("Authentication succeeded.");
// SSH接続後の処理
// リモートPC上でls -alコマンドを実行
rc = ExecCommand(sock, session);
if (rc != 0) {
DisConnect(sock, session);
return -1;
}
// SSH接続の終了
DisConnect(sock, session);
return 0;
}
void DisConnect(QTcpSocket &sock, LIBSSH2_SESSION *session)
{
// セッションの終了
if (session) {
libssh2_session_disconnect(session, "Normal Shutdown");
libssh2_session_free(session);
session = nullptr;
}
// ソケットを閉じる
if(sock.isOpen()) {
sock.close();
}
// libSSH2の終了
libssh2_exit();
return;
}
int ExecCommand(QTcpSocket &sock, LIBSSH2_SESSION *session)
{
// チャンネルをオープン
LIBSSH2_CHANNEL *channel = libssh2_channel_open_session(session);
if (!channel) {
qDebug() << "チャンネルのオープンに失敗";
return -1;
}
// リモートPC上でls -alコマンドを実行
if (libssh2_channel_exec(channel, "ls -la") != 0) {
qDebug() << "コマンドの実行に失敗";
return -1;
}
// ls -alコマンドの実行結果を取得
int rc = 0;
QByteArray buffer;
buffer.resize(1024); // バッファサイズを1024バイトに指定
do {
rc = libssh2_channel_read(channel, buffer.data(), buffer.size());
if (rc > 0) {
qDebug().noquote() << QString::fromUtf8(buffer.constData(), rc);
}
else if (rc < 0) {
qDebug() << "Error reading channel:" << rc;
break;
}
} while (rc > 0);
// チャンネルの終了を確認
// 終了コードおよび終了シグナルを取得して表示
int exitcode = 127;
char *exitsignal = nullptr;
rc = libssh2_channel_close(channel);
if (rc == 0) {
exitcode = libssh2_channel_get_exit_status(channel);
libssh2_channel_get_exit_signal(channel, &exitsignal, nullptr, nullptr, nullptr, nullptr, nullptr);
}
if (exitsignal) qDebug() << "Exit signal:" << exitsignal;
else qDebug() << "Exit code:" << exitcode;
return 0;
}
// DivideByZeroException.h
#ifndef DIVIDEBYZEROEXCEPTION_H
#define DIVIDEBYZEROEXCEPTION_H
#include <QException>
class DivideByZeroException : public QException
{
public:
DivideByZeroException() {};
virtual ~DivideByZeroException() {};
const char* what() const noexcept override {
return "Divide by zero exception";
}
};
#endif // DIVIDEBYZEROEXCEPTION_H
ノンブロッキングモード
以下の例では、リモート側のPCにSSH接続して、lsコマンドを実行してその結果をローカル側のPCに表示している。
以下の例は、ノンブロッキングモードでlsコマンドを実行している。
ノンブロッキングモード (シグナル / スロット) で設計する場合、libSSH2ライブラリの関数呼び出しは即座に返されて、処理がバックグラウンドで非同期に進行する。
これにより、プログラムは他の処理を続行でき、必要に応じてリモートホストとの通信が完了するのを待つことができる。
ノンブロッキングモードを使用する場合、特に入出力操作が発生する待ち時間を最小限に抑え、プログラムがより効率的に動作することが期待される。
しかし、ノンブロッキングモードを扱う際には、非同期処理やイベント駆動型のプログラミングに慣れる必要がある。
// SSHClient.hファイル
#include <QCoreApplication>
#include <QTcpSocket>
#include <QTimer>
#include <QDebug>
#include <libssh2.h>
class SSHClient : public QObject
{
Q_OBJECT
private:
QTcpSocket *m_Socket;
LIBSSH2_SESSION *m_Session;
LIBSSH2_CHANNEL *m_Channel;
QString m_Host;
quint16 m_Port;
QString m_User;
QString m_Passwaord;
QString m_PubKey;
QString m_PrivateKey;
QString m_Passphrase;
public:
SSHClient(QString host, quint16 port, QString user, QString password, QObject *parent = nullptr);
SSHClient(QString host, quint16 port, QString user,
QString pubkey, QString privkey, QString phrase, QObject *parent = nullptr);
~SSHClient();
void connectToHost();
private slots:
void onConnected();
void authenticateUser();
void openChannel();
void executeCommand();
void onReadyRead();
void readOutput();
void closeChannel();
};
// SSHClient.cppファイル
#include "SSHClient.h"
SSHClient::SSHClient(QString host, quint16 port, QString user, QString password, QObject *parent = nullptr) :
QObject(parent), m_Session(nullptr), m_Channel(nullptr), m_Host(host), m_Port(port), m_User(user), m_Password(password)
{
m_Socket = new QTcpSocket(this);
connect(m_Socket, &QTcpSocket::connected, this, &SSHClient::onConnected);
connect(m_Socket, &QTcpSocket::readyRead, this, &SSHClient::onReadyRead);
// libSSH2ライブラリの初期化
if (libssh2_init(0) != 0) {
qDebug() << "libSSH2ライブラリの初期化に失敗";
return;
}
}
SSHClient::SSHClient(QString host, quint16 port, QString user, QString pubkey, QString privkey, QString phrase, QObject *parent = nullptr) :
QObject(parent), m_Session(nullptr), m_Channel(nullptr),
m_Host(host), m_Port(port), m_User(user), m_PublicKey(pubkey), m_PrivateKey(privkey), m_Passphrase(phrase)
{
m_Socket = new QTcpSocket(this);
connect(m_Socket, &QTcpSocket::connected, this, &SSHClient::onConnected);
connect(m_Socket, &QTcpSocket::readyRead, this, &SSHClient::onReadyRead);
// libSSH2ライブラリの初期化
if (libssh2_init(0) != 0) {
qDebug() << "libSSH2ライブラリの初期化に失敗";
return;
}
}
SSHClient::~SSHClient()
{
if (m_Channel) libssh2_channel_free(m_Channel);
if (m_Session) {
libssh2_session_disconnect(m_Session, "Normal Shutdown");
libssh2_session_free(m_Session);
}
libssh2_exit();
}
void SSHClient::connectToHost()
{
m_Socket->connectToHost(m_Host, m_Port);
}
void SSHClient::onConnected()
{
qDebug() << "ホストへの接続に成功";
m_Session = libssh2_session_init();
libssh2_session_set_blocking(m_Session, 0);
int rc = 0;
do {
rc = libssh2_session_handshake(m_Session, m_Socket->socketDescriptor());
} while (rc == LIBSSH2_ERROR_EAGAIN);
if (rc) {
qDebug() << "SSHのセッションの確立に失敗:" << rc;
return;
}
authenticateUser();
}
void SSHClient::authenticateUser()
{
int rc = 0;
do {
// パスワード認証を行う場合
//rc = libssh2_userauth_password(m_Session,
// m_User.toUtf8().constData(),
// m_Password.toUtf8().constData());
// 公開鍵認証を行う場合
rc = libssh2_userauth_publickey_fromfile(m_Session,
m_User.toUtf8().constData(),
"", // クライアントPCに秘密鍵があり、
// リモートPC側に公開鍵が設置されている場合
m_PrivateKey.toUtf8().constData(),
m_Passphrase.isEmpty() ? nullptr : m_Passphrase.toUtf8().constData());
} while (rc == LIBSSH2_ERROR_EAGAIN);
if (rc) {
qDebug() << "認証に失敗:" << rc;
return;
}
openChannel();
}
void SSHClient::openChannel()
{
int rc = 0;
do {
m_Channel = libssh2_channel_open_session(m_Session);
} while (m_Channel == nullptr && libssh2_session_last_error(m_Session, nullptr, nullptr, 0) == LIBSSH2_ERROR_EAGAIN);
if (!m_Channel) {
qDebug() << "チャンネルのオープンに失敗";
return;
}
executeCommand();
}
void SSHClient::executeCommand()
{
int rc = 0;
do {
rc = libssh2_channel_exec(m_Channel, "ls -la");
} while (rc == LIBSSH2_ERROR_EAGAIN);
if (rc) {
qDebug() << "lsコマンドの実行に失敗:" << rc;
return;
}
// lsコマンドの出力結果を取得を開始
QTimer::singleShot(0, this, &SSHClient::readOutput);
}
void SSHClient::onReadyRead()
{
readOutput();
}
void SSHClient::readOutput()
{
if (!m_Channel) return;
QByteArray buffer;
buffer.resize(1024);
int rc = 0;
while (rc != LIBSSH2_ERROR_EAGAIN) {
rc = libssh2_channel_read(m_Channel, buffer.data(), buffer.size());
if (rc > 0) {
qDebug().noquote() << QString::fromUtf8(buffer.constData(), rc);
}
else if (rc < 0) {
qDebug() << "チャンネルの読み取りエラー:" << rc;
closeChannel();
return;
}
}
// チャンネルがEOFかどうかを確認
if (libssh2_channel_eof(m_Channel) == 1) {
closeChannel();
}
else {
// 続きを読み込む
QTimer::singleShot(0, this, &SSHClient::readOutput);
}
}
void SSHClient::closeChannel()
{
if (!m_Channel) return;
int rc = 0;
do {
rc = libssh2_channel_close(m_Channel);
} while (rc == LIBSSH2_ERROR_EAGAIN);
if (rc == 0) {
int exitcode = libssh2_channel_get_exit_status(m_Channel);
char *exitsignal = nullptr;
libssh2_channel_get_exit_signal(m_Channel, &exitsignal, nullptr, nullptr, nullptr, nullptr, nullptr);
if (exitsignal) {
qDebug() << "Exit signal:" << exitsignal;
}
else {
qDebug() << "Exit code:" << exitcode;
}
}
libssh2_channel_free(m_Channel);
m_Channel = nullptr;
// Disconnect from the host
m_Socket->disconnectFromHost();
}
// main.cppファイル
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// パスワード認証を行う場合
//SSHClient client("<リモートPC側のIPアドレスまたはホスト名>", <リモートPCのポート番号>,
// "<リモートPC側のユーザ名>", "<リモートPC側のユーザパスワード>");
// 公開鍵認証を行う場合
SSHClient client("<リモートPC側のIPアドレスまたはホスト名>", <リモートPCのポート番号>,
"<リモートPC側のユーザ名>",
"<公開鍵ファイルのパス>", // クライアントPCに秘密鍵があり、リモートPC側に公開鍵が設置されている場合は空欄 ("") にする
"<秘密鍵ファイルのパス>",
"<秘密鍵のパスフレーズ>");
client.connectToHost();
return a.exec();
}
SCPコマンドの使用例
libSSHライブラリおよびlibSSH2ライブラリを使用したSCPコマンドの使用例は、以下に示すページに記載している。
sshdコマンドの使用
OpenSSHのソースコードのダウンロード
OpenSSHの公式Webサイトにアクセスして、ソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。
tar xf openssh-<バージョン>.tar.gz
このディレクトリ内に必要なヘッダファイル等が存在する。
なお、OpenSSHのライセンスの全てのコンポーネントは、BSDライセンス、もしくはそれよりも自由なライセンスに属している。
sshd -tコマンド
以下の例では、openSSHライブラリを使用して、sshd -t
コマンドと同様、指定された設定ファイルの解析と妥当性を確認している。
initialize_server_options
関数を呼び出して、サーバオプションを初期化する。- コマンドライン引数からSSHの設定ファイルのパスを取得する。
引数が指定されていない場合は、デフォルトの"sshd_config"ファイルを使用する。 parse_server_config_depth
関数を呼び出して、指定されたSSHの設定ファイルを解析する。
解析に成功した場合は0
、失敗した場合は0以外
の値を返す。validate_server_config
関数を呼び出して、解析された設定ファイルの妥当性を確認する。
確認に成功した場合は0
、失敗した場合は0以外
の値を返す。- SSHの設定ファイルの解析と妥当性の結果に応じて、成功または失敗のメッセージを表示している。
#include <QCoreApplication>
#include <QDebug>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sshbuf.h>
#include <sshkey.h>
#include <authfile.h>
#include <auth-options.h>
#include <servconf.h>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ServerOptions options;
initialize_server_options(&options);
// コマンドライン引数からSSHの設定ファイルのパスを取得
QString configFile = "sshd_config";
if (a.arguments().size() > 1) {
configFile = a.arguments().at(1);
}
// SSHの設定ファイルの解析
if (parse_server_config_depth(&options, configFile.toUtf8().constData(), NULL, 0) != 0) {
qDebug() << QString("SSHの設定ファイルのパースに失敗: %1).arg(configFile);
return -1;
}
// SSHの設定ファイルの妥当性を確認
if (validate_server_config(&options) != 0) {
qDebug() << QString("SSHの設定ファイルに誤りがあります: %1").arg(configFile);
return -1;
}
qDebug() << QString("SSHの設定ファイル (%1) は正常です).arg(configFile);
return 0;
}
sshd -Tコマンド
以下の例では、openSSHライブラリを使用して、sshd -T
コマンドと同様、現在の設定オプションの値をダンプ形式で表示している。
initialize_server_options
関数を呼び出して、サーバオプションを初期化する。fill_default_server_options
関数を呼び出して、デフォルトのサーバオプションを設定する。parse_server_config_depth
関数を呼び出して、sshd_configファイルを解析する。
解析に成功した場合は0
、失敗した場合は0以外
の値を返す。- 解析に成功した場合は、
dump_config
関数を呼び出して、設定オプションのダンプを表示する。
これは、sshd -T
コマンドと同様、現在の設定オプションの値を表示する。
#include <QCoreApplication>
#include <QDebug>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sshbuf.h>
#include <sshkey.h>
#include <authfile.h>
#include <auth-options.h>
#include <servconf.h>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// SSHの設定ファイルオプション
ServerOptions options;
initialize_server_options(&options);
fill_default_server_options(&options);
if (parse_server_config_depth(&options, "sshd_config", NULL, 0) != 0) {
qDebug() << "sshd_configファイルのパースに失敗";
return -1;
}
qDebug() << "========= Dump of configuration options =========";
dump_config(&options);
qDebug() << "================================================";
return 0;
}