「Qtの基礎 - SSH」の版間の差分

提供: MochiuWiki : SUSE, EC, PCB

📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

421行目: 421行目:
以下の例では、リモート側のPCにSSH接続して、lsコマンドを実行してその結果をローカル側のPCに表示している。<br>
以下の例では、リモート側のPCにSSH接続して、lsコマンドを実行してその結果をローカル側のPCに表示している。<br>
<br>
<br>
以下の例は、<u>ノンブロッキングモード</u>でSCPを実行している。<br>
以下の例は、<u>ブロッキングモード</u>でlsコマンドを実行している。<br>
ノンブロッキングモードを有効にする場合、libSSH2ライブラリの関数呼び出しは即座に返されて、処理がバックグラウンドで非同期に進行する。<br>
もし、<u>ノンブロッキングモード</u> (シグナル / スロット) で設計する場合、libSSH2ライブラリの関数呼び出しは即座に返されて、処理がバックグラウンドで非同期に進行する。<br>
これにより、プログラムは他の処理を続行でき、必要に応じてリモートホストとの通信が完了するのを待つことができる。<br>
これにより、プログラムは他の処理を続行でき、必要に応じてリモートホストとの通信が完了するのを待つことができる。<br>
<br>
<br>
ノンブロッキングモードを使用する場合、特に入出力操作が発生する待ち時間を最小限に抑え、プログラムがより効率的に動作することが期待される。<br>
ノンブロッキングモードを使用する場合、特に入出力操作が発生する待ち時間を最小限に抑え、プログラムがより効率的に動作することが期待される。<br>
しかし、ノンブロッキングモードを扱う際には、非同期処理やイベント駆動型のプログラミングに慣れる必要がある。<br>
しかし、ノンブロッキングモードを扱う際には、非同期処理やイベント駆動型のプログラミングに慣れる必要がある。<br>
<br>
以下の例では、GNU LIBCを使用している場合およびQTcpSocketクラスを使用している場合の2つを、<code>#define NOQTCPSOCKET 1</code>プリプロセッサを使用して処理を別けている。<br>
<br>
有償版Qtライセンスを購入している場合、GNU LIBCライブラリを使用せずにQTcpSocketクラスを使用するならば、ライセンスにおいてリバースエンジニアリングを禁止することができる。<br>
<br>
GNU LIBCを使用している場合、(<code>#define NOQTCPSOCKET 1</code>プリプロセッサの使用する場合)<br>
インクルードしているLinux向けライブラリ群はglibc-devel (libc-dev) パッケージに含まれるため、LGPLライセンスであることに注意する。<br>
* sys/socket.h
*: ソケットプログラミングに必要なヘッダファイルである。
* arpa/inet.h
*: IPv4やIPv6アドレスの変換やネットワークバイトオーダーとホストバイトオーダーの変換等、ネットワークプログラミングで使用される。
* netinet/in.h
*: ネットワークプログラミングやソケットプログラミングに必要なヘッダファイルである。
* unistd.h
*: UNIXシステムのシステムコールやその他の標準的なシステム関数を宣言するためのヘッダファイルである。
*: ファイル操作、プロセス管理、システム情報の取得、メモリ管理、ファイルシステムの操作等の関数やマクロが含まれている。
* fcntl.h
*: ファイル制御やファイルディスクリプタ関連の操作に使用される。
<br>
<br>
  <syntaxhighlight lang="make">
  <syntaxhighlight lang="make">
468行目: 450行目:
   
   
  #include <QCoreApplication>
  #include <QCoreApplication>
  #include <QFileInfo>
  #include <QTcpSocket>
#include <QTimer>
#include <QElapsedTimer>
  #include <QDebug>
  #include <QDebug>
  #include <libssh2.h>
  #include <libssh2.h>
  #include "DivideByZeroException.h"
  #include "DivideByZeroException.h"
   
   
  #define NOQTCPSOCKET 1
  void DisConnect(QTcpSocket &sock, LIBSSH2_SESSION *session);
  int  ExecCommand(QTcpSocket &sock, LIBSSH2_SESSION *session);
#if NOQTCPSOCKET  // QTcpSocketクラスを使用しない場合
    #ifdef Q_OS_LINUX
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <fcntl.h>
    #endif
#else  // QTcpSocketクラスを使用する場合
    #include <QTcpSocket>
#endif
#if NOQTCPSOCKET  // QTcpSocketクラスを使用しない場合
    void DisConnect(int &sock, LIBSSH2_SESSION *session);
    int Send(int &sock, LIBSSH2_SESSION *session);
    int  waitsocket(libssh2_socket_t socket_fd, LIBSSH2_SESSION *session);
#else  // QTcpSocketクラスを使用する場合
    void DisConnect(QTcpSocket &sock, LIBSSH2_SESSION *session);
    int  Send(QTcpSocket &sock, LIBSSH2_SESSION *session);
#endif
   
   
  int main(int argc, char *argv[])
  int main(int argc, char *argv[])
502行目: 462行目:
     QCoreApplication a(argc, argv);
     QCoreApplication a(argc, argv);
   
   
     const char *fingerprint;
     int rc  = 0;
     LIBSSH2_SESSION *session;
     QTcpSocket sock;
    LIBSSH2_CHANNEL *channel;
     char *userauth_list;
     LIBSSH2_SESSION *session = nullptr;
   
   
     // libSSH2オブジェクトの初期化
     // libSSH2オブジェクトの初期化
511行目: 471行目:
     if (rc != 0) {
     if (rc != 0) {
         qDebug() << "libssh2 initialization failed";
         qDebug() << "libssh2 initialization failed";
        DisConnect(sock, session);
         return -1;
         return -1;
     }
     }
   
   
     // QTcpSocketクラスのインスタンスを生成して、リモートPCに接続
     // QTcpSocketクラスのインスタンスを生成して、リモートPCに接続
     QTcpSocket socket;
     sock.connectToHost(<リモートPCのIPアドレスまたはホスト名>, <リモートPCにSSH接続するポート番号>);
    socket.connectToHost(<リモートPCのIPアドレスまたはホスト名>, <リモートPCにSSH接続するポート番号>);
   
   
     // 最大10[秒]待機
     // 最大10[秒]待機
     if (!socket.waitForConnected(10000)) {
     if (!sock.waitForConnected(10000)) {
       qDebug() << "SSH接続に失敗:" << socket.errorString();
       qDebug() << "SSH接続に失敗:" << socket.errorString();
      DisConnect(sock, session);
       return -1;
       return -1;
     }
     }
   
   
     // セッションの初期化
     // SSHのセッションの初期化
     session = libssh2_session_init();
     session = libssh2_session_init();
     if (libssh2_session_handshake(session, socket.socketDescriptor()) != 0) {
    if (!session) {
       qDebug() << "SSHセッションの確立に失敗";
      qDebug() << "SSHのセッションの初期化に失敗";
      DisConnect(sock, session);
      return -1;
    }
    // SSHのハンドシェイク
     if (libssh2_session_handshake(session, sock.socketDescriptor()) != 0) {
       qDebug() << "SSHのハンドシェイクに失敗";
      DisConnect(sock, session);
       return -1;
       return -1;
     }
     }
   
   
     // リモートPCのフィンガープリントを確認
     // リモートPCのフィンガープリントを確認
     fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
     auto fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
     qDebug() << "リモートPCのフィンガープリント:" << fingerprint;
     qDebug() << "リモートPCのフィンガープリント:" << fingerprint;
   
   
     // 認証方法の確認
     // 認証方法の確認
     userauth_list = libssh2_userauth_list(session, USERNAME, strlen(USERNAME));
     auto userauth_list = libssh2_userauth_list(session, "<リモートPCのユーザ名>", strlen(USERNAME));
     qDebug() << "認証方法:" << userauth_list;
     qDebug() << "認証方法:" << userauth_list;
   
   
548行目: 521行目:
     if (libssh2_userauth_publickey_fromfile(session, "<リモートPCのユーザ名>", "<公開鍵ファイルのパス>", "<秘密鍵ファイルのパス>", "<秘密鍵のパスフレーズ>") != 0) {
     if (libssh2_userauth_publickey_fromfile(session, "<リモートPCのユーザ名>", "<公開鍵ファイルのパス>", "<秘密鍵ファイルのパス>", "<秘密鍵のパスフレーズ>") != 0) {
       qDebug() << "公開鍵認証に失敗";
       qDebug() << "公開鍵認証に失敗";
      DisConnect(sock, session);
       return -1;
       return -1;
     }
     }
   
   
     // チャンネルをオープン
     qDebug() << QString("Authentication succeeded.");
    channel = libssh2_channel_open_session(session);
    if (!channel) {
      qDebug() << "チャンネルのオープンに失敗";
      return -1;
    }
   
   
    // SSH接続後の処理
     // リモートPC上でls -alコマンドを実行
     // リモートPC上でls -alコマンドを実行
     if (libssh2_channel_exec(channel, "ls -la") != 0) {
     rc = ExecCommand(sock, session);
       qDebug() << "コマンドの実行に失敗";
    if (rc != 0) {
       DisConnect(sock, session);
       return -1;
       return -1;
     }
     }
   
   
     // ls -alコマンドの実行結果
     // SSH接続の終了
     char buffer[1024] = {};
     DisConnect(sock, session);
    while ((rc = libssh2_channel_read(channel, buffer, sizeof(buffer))) > 0) {
      qDebug().noquote() << QString::fromUtf8(buffer, rc);
    }
   
   
    // チャンネルをクローズ
     return 0;
    libssh2_channel_free(channel);
    // セッションのクローズ
    libssh2_session_disconnect(session, "Normal Shutdown");
    libssh2_session_free(session);
    // ソケットのクローズ
    socket.disconnectFromHost();
    // libssh2の初期化を解除
    libssh2_exit();
     return a.exec();
  }
  }
   
   
#if NOQTCPSOCKET  // QTcpSocketクラスを使用しない場合
void DisConnect(int &sock, LIBSSH2_SESSION *session)
{
    // セッションの終了
    if (session) {
      libssh2_session_disconnect(session, "Normal Shutdown");
      libssh2_session_free(session);
      session = nullptr;
    }
    // ソケットを閉じる
    if (sock != LIBSSH2_INVALID_SOCKET) {
      shutdown(sock, 2);
#if defined(Q_OS_WIN32) || defined(Q_OS_WIN64)
      closesocket(sock);
#else
      close(sock);
#endif
    }
    // libSSH2の終了
    libssh2_exit();
    return;
}
#else  // QTcpSocketクラスを使用する場合
  void DisConnect(QTcpSocket &sock, LIBSSH2_SESSION *session)
  void DisConnect(QTcpSocket &sock, LIBSSH2_SESSION *session)
  {
  {
633行目: 562行目:
     return;
     return;
  }
  }
#endif
   
   
#if NOQTCPSOCKET  // QTcpSocketクラスを使用しない場合
  int ExecCommand(QTcpSocket &sock, LIBSSH2_SESSION *session)
  int Send(int &sock, LIBSSH2_SESSION *session)
#else
int Send(QTcpSocket &sock, LIBSSH2_SESSION *session)
#endif
  {
  {
     // Send a file via scp. The mode parameter must only have permissions.
     // チャンネルをオープン
     LIBSSH2_CHANNEL *channel = nullptr;
     LIBSSH2_CHANNEL *channel = libssh2_channel_open_session(session);
     if (!channel) {
    QString localFilePath = "<ローカルPCのファイルパス  例: /tmp/hoge.png>";
       qDebug() << "チャンネルのオープンに失敗";
    QFile File(localFilePath);
     if(!File.open(QIODevice::ReadOnly)) {
       qDebug() << QString("Cannot open local File(%1) Open Error: %2").arg(QFileInfo(File).fileName(), File.errorString());
       return -1;
       return -1;
     }
     }
   
   
     // QFileInfoクラスではパーミッションは16進数のため、libssh2_scp_send64関数で使用するため8進数に変換する
     // リモートPC上でls -alコマンドを実行
    // これは、見た目の数値を同じにする必要がある  例: 0x644(16進数) --> 0644(8進数)
     if (libssh2_channel_exec(channel, "ls -la") != 0) {
     QFileInfo FileInfo(localFilePath);
       qDebug() << "コマンドの実行に失敗";
    //// ステッキービットは不要なため削除
    auto hexPermisshionString = QString::number(FileInfo.permissions(), 16).mid(1);
    bool ok;
    //// 8進数に変換
    unsigned int octPermisshionValue = hexPermisshionString.toInt(&ok, 8);
    if (!ok) {
       qDebug() << QString("Failed to convert permission value.");
       return -1;
       return -1;
     }
     }
   
   
     QString remoteFilePath = "<リモート側PCのファイルパス  例: /home/remote-user/hoge.png>";
     // ls -alコマンドの実行結果を取得
    int rc = 0;
    QByteArray buffer;
    buffer.resize(1024); // バッファサイズを1024バイトに指定
     do {
     do {
       channel = libssh2_scp_send64(session, remoteFilePath.toUtf8().constData(), octPermisshionValue & 0777, FileInfo.size(), 0, 0);
       rc = libssh2_channel_read(channel, buffer.data(), buffer.size());
       if (!channel && libssh2_session_last_errno(session) != LIBSSH2_ERROR_EAGAIN) {
       if (rc > 0) {
           File.close();
           qDebug().noquote() << QString::fromUtf8(buffer.constData(), rc);
          // チャンネルの終了
          if (channel) {
            libssh2_channel_free(channel);
            channel = nullptr;
          }
          char *err_msg;
          libssh2_session_last_error(session, &err_msg, NULL, 0);
          qDebug() << err_msg;
          return -1;
       }
       }
    } while (!channel);
      else if (rc < 0) {
          qDebug() << "Error reading channel:" << rc;
    qDebug() << QString("SCP session waiting to send file.");
    // タイマの開始
    QElapsedTimer elapsedTimer;
    elapsedTimer.start();
    int nread = 0,
        total = 0;
    char mem[1024 * 100] = {};
    do {
      nread = File.read(mem, sizeof(mem));
      if(nread <= 0) {
          // EOF
           break;
           break;
       }
       }
    } while (rc > 0);
   
   
        auto ptr = mem;
    // チャンネルの終了を確認
        total += nread;
    int  exitcode    = 127;
        auto prev = 0;
    char *exitsignal = nullptr;
        do {
    rc = libssh2_channel_close(channel);
          ssize_t nwritten;
    if (rc == 0) {
       exitcode = libssh2_channel_get_exit_status(channel);
#if NOQTCPSOCKET  // QTcpSocketクラスを使用しない場合
       libssh2_channel_get_exit_signal(channel, &exitsignal, nullptr, nullptr, nullptr, nullptr, nullptr);
          while ((nwritten = libssh2_channel_write(channel, ptr, nread)) == LIBSSH2_ERROR_EAGAIN) {
              waitsocket(sock, session);
              prev = 0;
          }
#else  // QTcpSocketクラスを使用する場合
          while ((nwritten = libssh2_channel_write(channel, ptr, nread)) == LIBSSH2_ERROR_EAGAIN) {
              prev = 0;
          }
          // 書き込み完了を待機
          sock.waitForBytesWritten();
#endif
          if (nwritten < 0) {
              qDebug() << QString("ERROR %1 total %2 / %3 prev %4").arg((int)nwritten)
                                                                  .arg((long)total)
                                                                  .arg((int)nread)
                                                                  .arg((int)prev);
              break;
          }
          else {
              prev = nread;
              // nwritten indicates how many bytes were written this time.
              nread -= nwritten;
              ptr += nwritten;
          }
      } while (nread);
    } while (!nread);  // only continue if nread was drained.
    File.close();
    // 経過時間をミリ秒単位で取得
    try {
       qint64 duration = elapsedTimer.elapsed() == 0 ? throw DivideByZeroException() : elapsedTimer.elapsed();
       qDebug() << QString("%1 bytes in %2 milli-seconds makes %3 bytes/sec").arg(static_cast<long>(total))
                                                                            .arg(duration)
                                                                            .arg(QString::number((double)((total * 1000) / duration), 'f', 1));
    }
    catch (const DivideByZeroException &ex) {
      qDebug() << ex.what();
      qDebug() << QString("Transfer rate could not be calculated.");
     }
     }
   
   
     qDebug() << QString("Sending EOF");
     if (exitsignal) qDebug() << "Exit signal:" << exitsignal;
     while(libssh2_channel_send_eof(channel) == LIBSSH2_ERROR_EAGAIN);
     else            qDebug() << "Exit code:" << exitcode;
    qDebug() << QString("Waiting for EOF");
    while(libssh2_channel_wait_eof(channel) == LIBSSH2_ERROR_EAGAIN);
    qDebug() << QString("Waiting for channel to close");
    while(libssh2_channel_wait_closed(channel) == LIBSSH2_ERROR_EAGAIN);
   
   
     return 0;
     return 0;
  }
  }
#if NOQTCPSOCKET  // QTcpSocketクラスを使用しない場合
int waitsocket(libssh2_socket_t socket_fd, LIBSSH2_SESSION *session)
{
    struct timeval timeout = {};
    timeout.tv_sec  = 10;
    timeout.tv_usec = 0;
    fd_set fd;
    FD_ZERO(&fd);
    FD_SET(socket_fd, &fd);
    // now make sure we wait in the correct direction.
    auto dir = libssh2_session_block_directions(session);
    fd_set *writefd = nullptr;
    fd_set *readfd  = nullptr;
    if(dir & LIBSSH2_SESSION_BLOCK_INBOUND)    readfd  = &fd;
    if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND)    writefd = &fd;
    auto rc = select((int)(socket_fd + 1), readfd, writefd, nullptr, &timeout);
    return rc;
}
#endif
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>

2024年7月31日 (水) 10:08時点における版

概要

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ディレクトリに配置される。

  • 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 session, QString &strErrMsg);
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    // SSHセッションの作成
    ssh_session 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(ssh_session, SSH_OPTIONS_HOST, host.toUtf8().data());
    ssh_options_set(ssh_session, SSH_OPTIONS_USER, user.toUtf8().data());
    ssh_options_set(ssh_session, SSH_OPTIONS_PORT_STR, port.toUtf8().data());
 
    // SSH接続
    int rc = ssh_connect(ssh_session);
    if (rc != SSH_OK) {
       // 接続に失敗した場合
       fprintf(stderr, "Error connecting to host: %s\n", ssh_get_error(ssh_session));
       ssh_free(ssh_session);
 
       return -1;
    }
 
    // ~/.sshディレクトリ等にあるファイルに記述されているサーバのIDを検証
    QString strErrMsg = "";
    if(VerifyKnownsHost(ssh_session, strErrMsg) < 0)
    {
       fprintf(stderr, "%s\n", strErrMsg.toUtf8().constData();
       if(ssh_session != nullptr)
       {
          ssh_disconnect(ssh_session);
          ssh_free(ssh_session);
       }
 
       return -1;
    }
 
    // 公開鍵認証
    // 秘密鍵の設定
    const char *private_key_path = "<秘密鍵のパス  例: /home/user/sshkey/id_rsa";
 
    // 秘密鍵のパスフレーズを設定していない場合
    rc = ssh_userauth_privatekey_file(ssh_session, nullptr, private_key_path, nullptr);
 
    // 秘密鍵のパスフレーズを設定している場合
    rc = ssh_userauth_privatekey_file(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(ssh_session);
 
    // SSHセッションの解放
    ssh_free(ssh_session);
 
    return 0;
 }
 
 int VerifyKnownsHost(ssh_session ssh_session, QString &strErrMsg)
 {
    // Authenticating the server.
    ssh_key srv_pubkey = {};
    if(ssh_get_server_publickey(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(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(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(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コマンドを実行している。
もし、ノンブロッキングモード (シグナル / スロット) で設計する場合、libSSH2ライブラリの関数呼び出しは即座に返されて、処理がバックグラウンドで非同期に進行する。
これにより、プログラムは他の処理を続行でき、必要に応じてリモートホストとの通信が完了するのを待つことができる。

ノンブロッキングモードを使用する場合、特に入出力操作が発生する待ち時間を最小限に抑え、プログラムがより効率的に動作することが期待される。
しかし、ノンブロッキングモードを扱う際には、非同期処理やイベント駆動型のプログラミングに慣れる必要がある。

 # .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



sshdコマンドの使用

OpenSSHのソースコードのダウンロード

OpenSSHの公式Webサイトにアクセスして、ソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。

tar xf openssh-<バージョン>.tar.gz


このディレクトリ内に必要なヘッダファイル等が存在する。

なお、OpenSSHのライセンスの全てのコンポーネントは、BSDライセンス、もしくはそれよりも自由なライセンスに属している。

sshd -tコマンド

以下の例では、openSSHライブラリを使用して、sshd -tコマンドと同様、指定された設定ファイルの解析と妥当性を確認している。

  1. initialize_server_options関数を呼び出して、サーバオプションを初期化する。
  2. コマンドライン引数からSSHの設定ファイルのパスを取得する。
    引数が指定されていない場合は、デフォルトの"sshd_config"ファイルを使用する。
  3. parse_server_config_depth関数を呼び出して、指定されたSSHの設定ファイルを解析する。
    解析に成功した場合は0、失敗した場合は0以外の値を返す。
  4. validate_server_config関数を呼び出して、解析された設定ファイルの妥当性を確認する。
    確認に成功した場合は0、失敗した場合は0以外の値を返す。
  5. 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コマンドと同様、現在の設定オプションの値をダンプ形式で表示している。

  1. initialize_server_options関数を呼び出して、サーバオプションを初期化する。
  2. fill_default_server_options関数を呼び出して、デフォルトのサーバオプションを設定する。
  3. parse_server_config_depth関数を呼び出して、sshd_configファイルを解析する。
    解析に成功した場合は0、失敗した場合は0以外の値を返す。
  4. 解析に成功した場合は、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;
 }