Browse Source

add serverctrl.

yuchuli 1 year ago
parent
commit
cae9773c54
94 changed files with 15438 additions and 0 deletions
  1. 73 0
      src/tool/serverctrl/.gitignore
  2. 11 0
      src/tool/serverctrl/main.cpp
  3. 120 0
      src/tool/serverctrl/mainwindow.cpp
  4. 46 0
      src/tool/serverctrl/mainwindow.h
  5. 107 0
      src/tool/serverctrl/mainwindow.ui
  6. 56 0
      src/tool/serverctrl/qssh/CMakeLists.txt
  7. BIN
      src/tool/serverctrl/qssh/images/dir.png
  8. BIN
      src/tool/serverctrl/qssh/images/help.png
  9. BIN
      src/tool/serverctrl/qssh/images/unknownfile.png
  10. 203 0
      src/tool/serverctrl/qssh/opensshkeyfilereader.cpp
  11. 73 0
      src/tool/serverctrl/qssh/opensshkeyfilereader_p.h
  12. 1 0
      src/tool/serverctrl/qssh/qssh.pri
  13. 123 0
      src/tool/serverctrl/qssh/qssh.pro
  14. 57 0
      src/tool/serverctrl/qssh/qssh.qbs
  15. 7 0
      src/tool/serverctrl/qssh/qssh.qrc
  16. 1175 0
      src/tool/serverctrl/qssh/sftpchannel.cpp
  17. 263 0
      src/tool/serverctrl/qssh/sftpchannel.h
  18. 135 0
      src/tool/serverctrl/qssh/sftpchannel_p.h
  19. 33 0
      src/tool/serverctrl/qssh/sftpdefs.cpp
  20. 119 0
      src/tool/serverctrl/qssh/sftpdefs.h
  21. 407 0
      src/tool/serverctrl/qssh/sftpfilesystemmodel.cpp
  22. 107 0
      src/tool/serverctrl/qssh/sftpfilesystemmodel.h
  23. 223 0
      src/tool/serverctrl/qssh/sftpincomingpacket.cpp
  24. 112 0
      src/tool/serverctrl/qssh/sftpincomingpacket_p.h
  25. 236 0
      src/tool/serverctrl/qssh/sftpoperation.cpp
  26. 290 0
      src/tool/serverctrl/qssh/sftpoperation_p.h
  27. 226 0
      src/tool/serverctrl/qssh/sftpoutgoingpacket.cpp
  28. 92 0
      src/tool/serverctrl/qssh/sftpoutgoingpacket_p.h
  29. 57 0
      src/tool/serverctrl/qssh/sftppacket.cpp
  30. 117 0
      src/tool/serverctrl/qssh/sftppacket_p.h
  31. 54 0
      src/tool/serverctrl/qssh/ssh_global.h
  32. 312 0
      src/tool/serverctrl/qssh/sshagent.cpp
  33. 125 0
      src/tool/serverctrl/qssh/sshagent_p.h
  34. 172 0
      src/tool/serverctrl/qssh/sshbotanconversions_p.h
  35. 180 0
      src/tool/serverctrl/qssh/sshcapabilities.cpp
  36. 91 0
      src/tool/serverctrl/qssh/sshcapabilities_p.h
  37. 277 0
      src/tool/serverctrl/qssh/sshchannel.cpp
  38. 125 0
      src/tool/serverctrl/qssh/sshchannel_p.h
  39. 413 0
      src/tool/serverctrl/qssh/sshchannelmanager.cpp
  40. 117 0
      src/tool/serverctrl/qssh/sshchannelmanager_p.h
  41. 1035 0
      src/tool/serverctrl/qssh/sshconnection.cpp
  42. 298 0
      src/tool/serverctrl/qssh/sshconnection.h
  43. 204 0
      src/tool/serverctrl/qssh/sshconnection_p.h
  44. 276 0
      src/tool/serverctrl/qssh/sshconnectionmanager.cpp
  45. 63 0
      src/tool/serverctrl/qssh/sshconnectionmanager.h
  46. 465 0
      src/tool/serverctrl/qssh/sshcryptofacility.cpp
  47. 149 0
      src/tool/serverctrl/qssh/sshcryptofacility_p.h
  48. 129 0
      src/tool/serverctrl/qssh/sshdirecttcpiptunnel.cpp
  49. 89 0
      src/tool/serverctrl/qssh/sshdirecttcpiptunnel.h
  50. 68 0
      src/tool/serverctrl/qssh/sshdirecttcpiptunnel_p.h
  51. 77 0
      src/tool/serverctrl/qssh/ssherrors.h
  52. 95 0
      src/tool/serverctrl/qssh/sshexception_p.h
  53. 100 0
      src/tool/serverctrl/qssh/sshforwardedtcpiptunnel.cpp
  54. 70 0
      src/tool/serverctrl/qssh/sshforwardedtcpiptunnel.h
  55. 44 0
      src/tool/serverctrl/qssh/sshforwardedtcpiptunnel_p.h
  56. 127 0
      src/tool/serverctrl/qssh/sshhostkeydatabase.cpp
  57. 75 0
      src/tool/serverctrl/qssh/sshhostkeydatabase.h
  58. 609 0
      src/tool/serverctrl/qssh/sshincomingpacket.cpp
  59. 252 0
      src/tool/serverctrl/qssh/sshincomingpacket_p.h
  60. 301 0
      src/tool/serverctrl/qssh/sshkeyexchange.cpp
  61. 105 0
      src/tool/serverctrl/qssh/sshkeyexchange_p.h
  62. 245 0
      src/tool/serverctrl/qssh/sshkeygenerator.cpp
  63. 83 0
      src/tool/serverctrl/qssh/sshkeygenerator.h
  64. 60 0
      src/tool/serverctrl/qssh/sshkeypasswordretriever.cpp
  65. 47 0
      src/tool/serverctrl/qssh/sshkeypasswordretriever_p.h
  66. 37 0
      src/tool/serverctrl/qssh/sshlogging.cpp
  67. 42 0
      src/tool/serverctrl/qssh/sshlogging_p.h
  68. 421 0
      src/tool/serverctrl/qssh/sshoutgoingpacket.cpp
  69. 125 0
      src/tool/serverctrl/qssh/sshoutgoingpacket_p.h
  70. 163 0
      src/tool/serverctrl/qssh/sshpacket.cpp
  71. 157 0
      src/tool/serverctrl/qssh/sshpacket_p.h
  72. 149 0
      src/tool/serverctrl/qssh/sshpacketparser.cpp
  73. 83 0
      src/tool/serverctrl/qssh/sshpacketparser_p.h
  74. 118 0
      src/tool/serverctrl/qssh/sshpseudoterminal.h
  75. 401 0
      src/tool/serverctrl/qssh/sshremoteprocess.cpp
  76. 143 0
      src/tool/serverctrl/qssh/sshremoteprocess.h
  77. 118 0
      src/tool/serverctrl/qssh/sshremoteprocess_p.h
  78. 283 0
      src/tool/serverctrl/qssh/sshremoteprocessrunner.cpp
  79. 98 0
      src/tool/serverctrl/qssh/sshremoteprocessrunner.h
  80. 288 0
      src/tool/serverctrl/qssh/sshsendfacility.cpp
  81. 122 0
      src/tool/serverctrl/qssh/sshsendfacility_p.h
  82. 137 0
      src/tool/serverctrl/qssh/sshtcpipforwardserver.cpp
  83. 81 0
      src/tool/serverctrl/qssh/sshtcpipforwardserver.h
  84. 54 0
      src/tool/serverctrl/qssh/sshtcpipforwardserver_p.h
  85. 123 0
      src/tool/serverctrl/qssh/sshtcpiptunnel.cpp
  86. 82 0
      src/tool/serverctrl/qssh/sshtcpiptunnel_p.h
  87. 222 0
      src/tool/serverctrl/qssh/sshx11channel.cpp
  88. 73 0
      src/tool/serverctrl/qssh/sshx11channel_p.h
  89. 49 0
      src/tool/serverctrl/qssh/sshx11displayinfo_p.h
  90. 161 0
      src/tool/serverctrl/qssh/sshx11inforetriever.cpp
  91. 65 0
      src/tool/serverctrl/qssh/sshx11inforetriever_p.h
  92. 140 0
      src/tool/serverctrl/remoteprocess.cpp
  93. 66 0
      src/tool/serverctrl/remoteprocess.h
  94. 36 0
      src/tool/serverctrl/serverctrl.pro

+ 73 - 0
src/tool/serverctrl/.gitignore

@@ -0,0 +1,73 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+

+ 11 - 0
src/tool/serverctrl/main.cpp

@@ -0,0 +1,11 @@
+#include "mainwindow.h"
+
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+    QApplication a(argc, argv);
+    MainWindow w;
+    w.show();
+    return a.exec();
+}

+ 120 - 0
src/tool/serverctrl/mainwindow.cpp

@@ -0,0 +1,120 @@
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+using namespace QSsh;
+
+#include <iostream>
+
+MainWindow::MainWindow(QWidget *parent)
+    : QMainWindow(parent)
+    , ui(new Ui::MainWindow)
+{
+    ui->setupUi(this);
+
+    SshConnectionParameters param;
+    param.setHost("223.84.137.240");
+    param.setUserName("ykjs");
+    param.setPassword("admin@123");
+    param.setPort(4096);
+    param.authenticationType = SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods;
+    param.timeout = 30;
+
+    mpRP = new RemoteProcess(param);
+
+    connect(mpRP,SIGNAL(ServiceState(int )),this,SLOT(onUpdateState(int)));
+
+
+    ResetPush();
+
+    mpTimer = new QTimer(this);
+    mpTimer->setInterval(2000);
+    connect(mpTimer,SIGNAL(timeout()),this,SLOT(onTimer()));
+//    mpRP->run();
+}
+
+MainWindow::~MainWindow()
+{
+    delete mpRP;
+    delete ui;
+}
+
+
+void MainWindow::on_pushButton_ConnectServer_clicked()
+{
+    mpRP->run();
+}
+
+void MainWindow::onUpdateState(int nState)
+{
+    ui->pushButton_StopRM->setEnabled(false);
+    ui->pushButton_StartRM->setEnabled(false);
+    ui->pushButton_StartGroup->setEnabled(false);
+    ui->pushButton_StopGroup->setEnabled(false);
+
+    switch (nState) {
+    case 0:
+        ui->pushButton_StartRM->setEnabled(true);
+        ui->pushButton_StartGroup->setEnabled(true);
+        break;
+    case 1:
+        ui->pushButton_StopRM->setEnabled(true);
+        ui->pushButton_StartGroup->setEnabled(true);
+        break;
+    case 2:
+        ui->pushButton_StartRM->setEnabled(true);
+        ui->pushButton_StopGroup->setEnabled(true);
+        break;
+    case 3:
+        ui->pushButton_StopRM->setEnabled(true);
+        ui->pushButton_StopGroup->setEnabled(true);
+        break;
+    default:
+        break;
+
+    }
+
+    ui->pushButton_ConnectServer->setEnabled(false);
+}
+
+void MainWindow::on_pushButton_StartRM_clicked()
+{
+    ResetPush();
+    mpRP->runCmd("./startrm.sh");
+    mpTimer->start();
+}
+
+void MainWindow::on_pushButton_StartGroup_clicked()
+{
+    ResetPush();
+    mpRP->runCmd("./startgroup.sh");
+    mpTimer->start();
+}
+
+void MainWindow::on_pushButton_StopRM_clicked()
+{
+    ResetPush();
+    mpRP->runCmd("./stoprm.sh");
+    mpTimer->start();
+}
+
+void MainWindow::on_pushButton_StopGroup_clicked()
+{
+    ResetPush();
+    mpRP->runCmd("./stopgroup.sh");
+    mpTimer->start();
+}
+
+void MainWindow::onTimer()
+{
+    std::cout<<"timer."<<std::endl;
+    mpRP->run();
+    mpTimer->stop();
+}
+
+void MainWindow::ResetPush()
+{
+    ui->pushButton_StopRM->setEnabled(false);
+    ui->pushButton_StartRM->setEnabled(false);
+    ui->pushButton_StartGroup->setEnabled(false);
+    ui->pushButton_StopGroup->setEnabled(false);
+
+}

+ 46 - 0
src/tool/serverctrl/mainwindow.h

@@ -0,0 +1,46 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+#include <QTimer>
+
+#include "remoteprocess.h"
+
+QT_BEGIN_NAMESPACE
+namespace Ui { class MainWindow; }
+QT_END_NAMESPACE
+
+class MainWindow : public QMainWindow
+{
+    Q_OBJECT
+
+public:
+    MainWindow(QWidget *parent = nullptr);
+    ~MainWindow();
+
+private slots:
+    void on_pushButton_ConnectServer_clicked();
+
+    void onUpdateState(int nState);
+
+    void on_pushButton_StartRM_clicked();
+
+    void on_pushButton_StartGroup_clicked();
+
+    void on_pushButton_StopRM_clicked();
+
+    void on_pushButton_StopGroup_clicked();
+
+    void onTimer();
+
+private:
+    Ui::MainWindow *ui;
+
+    RemoteProcess* mpRP;
+
+    QTimer * mpTimer;
+
+    void ResetPush();
+};
+#endif // MAINWINDOW_H

+ 107 - 0
src/tool/serverctrl/mainwindow.ui

@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>768</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <widget class="QPushButton" name="pushButton_ConnectServer">
+    <property name="geometry">
+     <rect>
+      <x>50</x>
+      <y>30</y>
+      <width>151</width>
+      <height>51</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>连接服务器</string>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="pushButton_StartRM">
+    <property name="geometry">
+     <rect>
+      <x>99</x>
+      <y>110</y>
+      <width>231</width>
+      <height>81</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>开启远程驾驶服务</string>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="pushButton_StopRM">
+    <property name="geometry">
+     <rect>
+      <x>443</x>
+      <y>110</y>
+      <width>231</width>
+      <height>81</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>关闭远程驾驶服务</string>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="pushButton_StartGroup">
+    <property name="geometry">
+     <rect>
+      <x>99</x>
+      <y>230</y>
+      <width>231</width>
+      <height>81</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>开启编队服务</string>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="pushButton_StopGroup">
+    <property name="geometry">
+     <rect>
+      <x>444</x>
+      <y>230</y>
+      <width>231</width>
+      <height>81</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>关闭编队服务</string>
+    </property>
+   </widget>
+   <widget class="QPlainTextEdit" name="plainTextEdit">
+    <property name="geometry">
+     <rect>
+      <x>50</x>
+      <y>340</y>
+      <width>671</width>
+      <height>171</height>
+     </rect>
+    </property>
+   </widget>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>768</width>
+     <height>28</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 56 - 0
src/tool/serverctrl/qssh/CMakeLists.txt

@@ -0,0 +1,56 @@
+add_library(QSsh
+    sshsendfacility.cpp
+    sshremoteprocess.cpp
+    sshpacketparser.cpp
+    sshpacket.cpp
+    sshoutgoingpacket.cpp
+    sshkeygenerator.cpp
+    sshkeyexchange.cpp
+    sshincomingpacket.cpp
+    sshcryptofacility.cpp
+    sshconnection.cpp
+    sshchannelmanager.cpp
+    sshchannel.cpp
+    sshcapabilities.cpp
+    sftppacket.cpp
+    sftpoutgoingpacket.cpp
+    sftpoperation.cpp
+    sftpincomingpacket.cpp
+    sftpdefs.cpp
+    sftpchannel.cpp
+    sshremoteprocessrunner.cpp
+    sshconnectionmanager.cpp
+    sshkeypasswordretriever.cpp
+    sftpfilesystemmodel.cpp
+    sshdirecttcpiptunnel.cpp
+    sshhostkeydatabase.cpp
+    sshlogging.cpp
+    sshtcpipforwardserver.cpp
+    sshtcpiptunnel.cpp
+    sshforwardedtcpiptunnel.cpp
+    sshagent.cpp
+    sshx11channel.cpp
+    sshx11inforetriever.cpp
+    opensshkeyfilereader.cpp
+    qssh.qrc)
+
+get_property(BOTAN_LIB GLOBAL PROPERTY BOTAN_LIB)
+target_link_libraries( QSsh Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Widgets ${BOTAN_LIB})
+
+# state that anybody linking to us needs to include the current source dir
+target_include_directories(QSsh
+          INTERFACE
+		  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
+		  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>
+		  $<INSTALL_INTERFACE:include>
+          $<INSTALL_INTERFACE:include/qssh>
+          )
+		  
+#INSTALL RULES
+install(
+  DIRECTORY .
+  DESTINATION include
+  COMPONENT headers
+  FILES_MATCHING PATTERN "*.hpp" PATTERN "*.h"
+)
+install(TARGETS QSsh DESTINATION lib EXPORT QSsh-targets)

BIN
src/tool/serverctrl/qssh/images/dir.png


BIN
src/tool/serverctrl/qssh/images/help.png


BIN
src/tool/serverctrl/qssh/images/unknownfile.png


+ 203 - 0
src/tool/serverctrl/qssh/opensshkeyfilereader.cpp

@@ -0,0 +1,203 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "opensshkeyfilereader_p.h"
+
+#include "sshcapabilities_p.h"
+#include "ssherrors.h"
+#include "sshexception_p.h"
+#include "sshlogging_p.h"
+#include "sshpacketparser_p.h"
+#include "ssh_global.h"
+
+#include <botan/dl_group.h>
+#include <botan/dsa.h>
+#include <botan/ecdsa.h>
+#include <botan/rsa.h>
+
+#include <memory>
+
+namespace QSsh {
+namespace Internal {
+
+using namespace Botan;
+
+bool OpenSshKeyFileReader::parseKey(const QByteArray &privKeyFileContents)
+{
+    static const QByteArray magicPrefix = "-----BEGIN OPENSSH PRIVATE KEY-----\n";
+    static const QByteArray magicSuffix = "-----END OPENSSH PRIVATE KEY-----\n";
+    if (!privKeyFileContents.startsWith(magicPrefix)) {
+        qCDebug(sshLog) << "not an OpenSSH key file: prefix does not match";
+        return false;
+    }
+    if (!privKeyFileContents.endsWith(magicSuffix))
+        throwException(SSH_TR("Unexpected end-of-file marker."));
+    const QByteArray payload = QByteArray::fromBase64
+            (privKeyFileContents.mid(magicPrefix.size(), privKeyFileContents.size()
+                                     - magicPrefix.size() - magicSuffix.size()));
+    doParse(payload);
+    return true;
+}
+
+std::unique_ptr<Private_Key> OpenSshKeyFileReader::privateKey() const
+{
+    if (m_keyType == SshCapabilities::PubKeyRsa) {
+        QSSH_ASSERT_AND_RETURN_VALUE(m_parameters.size() == 5, nullptr);
+        const BigInt &e = m_parameters.at(0);
+        const BigInt &n = m_parameters.at(1);
+        const BigInt &p = m_parameters.at(2);
+        const BigInt &q = m_parameters.at(3);
+        const BigInt &d = m_parameters.at(4);
+        return std::make_unique<RSA_PrivateKey>(p, q, e, d, n);
+    } else if (m_keyType == SshCapabilities::PubKeyDss) {
+        QSSH_ASSERT_AND_RETURN_VALUE(m_parameters.size() == 5, nullptr);
+        const BigInt &p = m_parameters.at(0);
+        const BigInt &q = m_parameters.at(1);
+        const BigInt &g = m_parameters.at(2);
+        const BigInt &x = m_parameters.at(4);
+        return std::make_unique<DSA_PrivateKey>(m_rng, DL_Group(p, q, g), x);
+    } else if (m_keyType.startsWith(SshCapabilities::PubKeyEcdsaPrefix)) {
+        QSSH_ASSERT_AND_RETURN_VALUE(m_parameters.size() == 1, nullptr);
+        const BigInt &value = m_parameters.first();
+        const EC_Group group(SshCapabilities::oid(m_keyType));
+        return std::make_unique<ECDSA_PrivateKey>(m_rng, group, value);
+    }
+    QSSH_ASSERT_AND_RETURN_VALUE(false, nullptr);
+}
+
+QList<BigInt> OpenSshKeyFileReader::publicParameters() const
+{
+    if (m_keyType == SshCapabilities::PubKeyRsa)
+        return m_parameters.mid(0, 2);
+    if (m_keyType == SshCapabilities::PubKeyDss)
+        return m_parameters.mid(0, 4);
+    if (m_keyType.startsWith(SshCapabilities::PubKeyEcdsaPrefix))
+        return QList<BigInt>();
+    QSSH_ASSERT_AND_RETURN_VALUE(false, QList<BigInt>());
+}
+
+void OpenSshKeyFileReader::doParse(const QByteArray &payload)
+{
+    // See PROTOCOL.key in OpenSSH sources.
+    static const QByteArray magicString = "openssh-key-v1";
+    if (!payload.startsWith(magicString))
+        throwException(SSH_TR("Unexpected magic string."));
+    try {
+        quint32 offset = magicString.size() + 1; // null byte
+        m_cipherName = SshPacketParser::asString(payload, &offset);
+        qCDebug(sshLog) << "cipher:" << m_cipherName;
+        m_kdf = SshPacketParser::asString(payload, &offset);
+        qCDebug(sshLog) << "kdf:" << m_kdf;
+        parseKdfOptions(SshPacketParser::asString(payload, &offset));
+        const quint32 keyCount = SshPacketParser::asUint32(payload, &offset);
+        if (keyCount != 1) {
+            qCWarning(sshLog) << "more than one key found in OpenSSH private key file, ignoring "
+                                 "all but the first one";
+        }
+        for (quint32 i = 0; i < keyCount; ++i) // Skip the public key blob(s).
+            SshPacketParser::asString(payload, &offset);
+        m_privateKeyList = SshPacketParser::asString(payload, &offset);
+        decryptPrivateKeyList();
+        parsePrivateKeyList();
+    } catch (const SshPacketParseException &) {
+        throwException(SSH_TR("Parse error."));
+    } catch (const Exception &e) {
+        throwException(QString::fromLocal8Bit(e.what()));
+    }
+}
+
+void OpenSshKeyFileReader::parseKdfOptions(const QByteArray &kdfOptions)
+{
+    if (m_cipherName == "none")
+        return;
+    quint32 offset = 0;
+    m_salt = SshPacketParser::asString(kdfOptions, &offset);
+    if (m_salt.size() != 16)
+        throwException(SSH_TR("Invalid salt size %1.").arg(m_salt.size()));
+    m_rounds = SshPacketParser::asUint32(kdfOptions, &offset);
+    qCDebug(sshLog) << "salt:" << m_salt.toHex();
+    qCDebug(sshLog) << "rounds:" << m_rounds;
+}
+
+void OpenSshKeyFileReader::decryptPrivateKeyList()
+{
+    if (m_cipherName == "none")
+        return;
+    if (m_kdf != "bcrypt") {
+        throwException(SSH_TR("Unexpected key derivation function '%1'.")
+                       .arg(QString::fromLatin1(m_kdf)));
+    }
+
+    // OpenSSH uses a proprietary algorithm for the key derivation. We'd basically have to
+    // copy the code.
+    // TODO: If the lower-level operations (hashing primitives, blowfish stuff) can be taken
+    //       over by Botan, that might be feasible. Investigate.
+    throwException(SSH_TR("Encrypted keys are currently not supported in this format."));
+}
+
+void OpenSshKeyFileReader::parsePrivateKeyList()
+{
+    quint32 offset = 0;
+    const quint32 checkInt1 = SshPacketParser::asUint32(m_privateKeyList, &offset);
+    const quint32 checkInt2 = SshPacketParser::asUint32(m_privateKeyList, &offset);
+    if (checkInt1 != checkInt2)
+        throwException(SSH_TR("Verification failed."));
+    m_keyType = SshPacketParser::asString(m_privateKeyList, &offset);
+    qCDebug(sshLog) << "key type:" << m_keyType;
+    if (m_keyType == SshCapabilities::PubKeyRsa) {
+        const BigInt n = SshPacketParser::asBigInt(m_privateKeyList, &offset);
+        const BigInt e = SshPacketParser::asBigInt(m_privateKeyList, &offset);
+        const BigInt d = SshPacketParser::asBigInt(m_privateKeyList, &offset);
+        SshPacketParser::asBigInt(m_privateKeyList, &offset); // iqmp
+        const BigInt p = SshPacketParser::asBigInt(m_privateKeyList, &offset);
+        const BigInt q = SshPacketParser::asBigInt(m_privateKeyList, &offset);
+        m_parameters = QList<BigInt>{e, n, p, q, d};
+    } else if (m_keyType == SshCapabilities::PubKeyDss) {
+        const BigInt p = SshPacketParser::asBigInt(m_privateKeyList, &offset);
+        const BigInt q = SshPacketParser::asBigInt(m_privateKeyList, &offset);
+        const BigInt g = SshPacketParser::asBigInt(m_privateKeyList, &offset);
+        const BigInt y = SshPacketParser::asBigInt(m_privateKeyList, &offset);
+        const BigInt x = SshPacketParser::asBigInt(m_privateKeyList, &offset);
+        m_parameters = QList<BigInt>{p, q, g, y, x};
+    } else if (m_keyType.startsWith(SshCapabilities::PubKeyEcdsaPrefix)) {
+        SshPacketParser::asString(m_privateKeyList, &offset); // name
+        SshPacketParser::asString(m_privateKeyList, &offset); // pubkey representation
+        m_parameters = {SshPacketParser::asBigInt(m_privateKeyList, &offset)};
+    } else {
+        throwException(SSH_TR("Private key type '%1' is not supported.")
+                       .arg(QString::fromLatin1(m_keyType)));
+    }
+    const QByteArray comment = SshPacketParser::asString(m_privateKeyList, &offset);
+    qCDebug(sshLog) << "comment:" << comment;
+}
+
+void OpenSshKeyFileReader::throwException(const QString &reason)
+{
+    throw SshClientException(SshKeyFileError,
+                             SSH_TR("Processing OpenSSH private key file failed: %1").arg(reason));
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 73 - 0
src/tool/serverctrl/qssh/opensshkeyfilereader_p.h

@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QByteArray>
+#include <QList>
+
+#include <botan/bigint.h>
+
+#include <memory>
+
+namespace Botan {
+class Private_Key;
+class RandomNumberGenerator;
+}
+
+namespace QSsh {
+namespace Internal {
+
+class OpenSshKeyFileReader
+{
+public:
+    OpenSshKeyFileReader(Botan::RandomNumberGenerator &rng) : m_rng(rng) {}
+
+    bool parseKey(const QByteArray &privKeyFileContents);
+    QByteArray keyType() const { return m_keyType; }
+    std::unique_ptr<Botan::Private_Key> privateKey() const;
+    QList<Botan::BigInt> allParameters() const { return m_parameters; }
+    QList<Botan::BigInt> publicParameters() const;
+
+private:
+    void doParse(const QByteArray &payload);
+    void parseKdfOptions(const QByteArray &kdfOptions);
+    void decryptPrivateKeyList();
+    void parsePrivateKeyList();
+    [[noreturn]] void throwException(const QString &reason);
+
+    Botan::RandomNumberGenerator &m_rng;
+    QByteArray m_keyType;
+    QList<Botan::BigInt> m_parameters;
+    QByteArray m_cipherName;
+    QByteArray m_kdf;
+    QByteArray m_salt;
+    quint32 m_rounds;
+    QByteArray m_privateKeyList;
+};
+
+} // namespace Internal
+} // namespace QSsh
+

+ 1 - 0
src/tool/serverctrl/qssh/qssh.pri

@@ -0,0 +1 @@
+LIBS *= -l$$qtLibraryName(QSsh)

+ 123 - 0
src/tool/serverctrl/qssh/qssh.pro

@@ -0,0 +1,123 @@
+TEMPLATE = lib
+TARGET = QSsh
+QT += network
+DEFINES += QTCSSH_LIBRARY
+
+#Enable debug log
+#DEFINES += CREATOR_SSH_DEBUG
+
+INCLUDEPATH += $$QSSH_PREFIX/include/botan-2/
+DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050F00
+
+include(../../../qssh.pri)
+
+win32 {
+    DLLDESTDIR = $$[QT_INSTALL_LIBS]
+}
+
+!win32-msvc* {
+    QMAKE_CXXFLAGS += -Wextra -pedantic
+}
+
+DESTDIR = $$IDE_LIBRARY_PATH
+
+TARGET = $$qtLibraryName($$TARGET)
+
+CONFIG += shared dll warn_on
+
+DEFINES += QT_DEPRECATED_WARNINGS
+
+contains(QT_CONFIG, reduce_exports):CONFIG += hide_symbols
+
+SOURCES = $$PWD/sshsendfacility.cpp \
+    $$PWD/sshremoteprocess.cpp \
+    $$PWD/sshpacketparser.cpp \
+    $$PWD/sshpacket.cpp \
+    $$PWD/sshoutgoingpacket.cpp \
+    $$PWD/sshkeygenerator.cpp \
+    $$PWD/sshkeyexchange.cpp \
+    $$PWD/sshincomingpacket.cpp \
+    $$PWD/sshcryptofacility.cpp \
+    $$PWD/sshconnection.cpp \
+    $$PWD/sshchannelmanager.cpp \
+    $$PWD/sshchannel.cpp \
+    $$PWD/sshcapabilities.cpp \
+    $$PWD/sftppacket.cpp \
+    $$PWD/sftpoutgoingpacket.cpp \
+    $$PWD/sftpoperation.cpp \
+    $$PWD/sftpincomingpacket.cpp \
+    $$PWD/sftpdefs.cpp \
+    $$PWD/sftpchannel.cpp \
+    $$PWD/sshremoteprocessrunner.cpp \
+    $$PWD/sshconnectionmanager.cpp \
+    $$PWD/sshkeypasswordretriever.cpp \
+    $$PWD/sftpfilesystemmodel.cpp \
+    $$PWD/sshdirecttcpiptunnel.cpp \
+    $$PWD/sshhostkeydatabase.cpp \
+    $$PWD/sshlogging.cpp \
+    $$PWD/sshtcpipforwardserver.cpp \
+    $$PWD/sshtcpiptunnel.cpp \
+    $$PWD/sshforwardedtcpiptunnel.cpp \
+    $$PWD/sshagent.cpp \
+    $$PWD/sshx11channel.cpp \
+    $$PWD/sshx11inforetriever.cpp \
+    $$PWD/opensshkeyfilereader.cpp \
+
+PUBLIC_HEADERS = \
+    $$PWD/sftpdefs.h \
+    $$PWD/ssherrors.h \
+    $$PWD/sshremoteprocess.h \
+    $$PWD/sftpchannel.h \
+    $$PWD/sshkeygenerator.h \
+    $$PWD/sshremoteprocessrunner.h \
+    $$PWD/sshconnectionmanager.h \
+    $$PWD/sshpseudoterminal.h \
+    $$PWD/sftpfilesystemmodel.h \
+    $$PWD/sshdirecttcpiptunnel.h \
+    $$PWD/sshtcpipforwardserver.h \
+    $$PWD/sshhostkeydatabase.h \
+    $$PWD/sshforwardedtcpiptunnel.h \
+    $$PWD/ssh_global.h \
+    $$PWD/sshconnection.h \
+
+HEADERS = $$PUBLIC_HEADERS \
+    $$PWD/sshsendfacility_p.h \
+    $$PWD/sshremoteprocess_p.h \
+    $$PWD/sshpacketparser_p.h \
+    $$PWD/sshpacket_p.h \
+    $$PWD/sshoutgoingpacket_p.h \
+    $$PWD/sshkeyexchange_p.h \
+    $$PWD/sshincomingpacket_p.h \
+    $$PWD/sshexception_p.h \
+    $$PWD/sshcryptofacility_p.h \
+    $$PWD/sshconnection_p.h \
+    $$PWD/sshchannelmanager_p.h \
+    $$PWD/sshchannel_p.h \
+    $$PWD/sshcapabilities_p.h \
+    $$PWD/sshbotanconversions_p.h \
+    $$PWD/sftppacket_p.h \
+    $$PWD/sftpoutgoingpacket_p.h \
+    $$PWD/sftpoperation_p.h \
+    $$PWD/sftpincomingpacket_p.h \
+    $$PWD/sftpchannel_p.h \
+    $$PWD/sshkeypasswordretriever_p.h \
+    $$PWD/sshdirecttcpiptunnel_p.h \
+    $$PWD/sshlogging_p.h \
+    $$PWD/sshtcpipforwardserver_p.h \
+    $$PWD/sshtcpiptunnel_p.h \
+    $$PWD/sshforwardedtcpiptunnel_p.h \
+    $$PWD/sshagent_p.h \
+    $$PWD/sshx11channel_p.h \
+    $$PWD/sshx11displayinfo_p.h \
+    $$PWD/sshx11inforetriever_p.h \
+    $$PWD/opensshkeyfilereader_p.h \
+
+RESOURCES += $$PWD/qssh.qrc
+
+
+headers.files = $$PUBLIC_HEADERS
+headers.path = $$[QT_INSTALL_PREFIX]/include/QSsh
+
+target.path = $$[QT_INSTALL_LIBS]
+
+INSTALLS += target headers

+ 57 - 0
src/tool/serverctrl/qssh/qssh.qbs

@@ -0,0 +1,57 @@
+import qbs.base 1.0
+import "../QtcLibrary.qbs" as QtcLibrary
+
+QtcLibrary {
+    name: "QtcSsh"
+
+    cpp.defines: base.concat(["QSSH_LIBRARY"])
+    cpp.includePaths: [
+        ".",
+        "..",
+        "../..",
+        buildDirectory
+    ]
+
+    Depends { name: "cpp" }
+    Depends { name: "Qt"; submodules: ["widgets", "network" ] }
+    Depends { name: "Botan" }
+
+    files: [
+        "sftpchannel.h", "sftpchannel_p.h", "sftpchannel.cpp",
+        "sftpdefs.cpp", "sftpdefs.h",
+        "sftpincomingpacket.cpp", "sftpincomingpacket_p.h",
+        "sftpoperation.cpp", "sftpoperation_p.h",
+        "sftpoutgoingpacket.cpp", "sftpoutgoingpacket_p.h",
+        "sftppacket.cpp", "sftppacket_p.h",
+        "sshcapabilities_p.h", "sshcapabilities.cpp",
+        "sshchannel.cpp", "sshchannel_p.h",
+        "sshchannelmanager.cpp", "sshchannelmanager_p.h",
+        "sshconnection.h", "sshconnection_p.h", "sshconnection.cpp",
+        "sshconnectionmanager.cpp", "sshconnectionmanager.h",
+        "sshcryptofacility.cpp", "sshcryptofacility_p.h",
+        "sshkeyexchange.cpp", "sshkeyexchange_p.h",
+        "sshkeypasswordretriever_p.h",
+        "sshoutgoingpacket.cpp", "sshoutgoingpacket_p.h",
+        "sshpacket.cpp", "sshpacket_p.h",
+        "sshpacketparser.cpp", "sshpacketparser_p.h",
+        "sshremoteprocess.cpp", "sshremoteprocess.h", "sshremoteprocess_p.h",
+        "sshdirecttcpiptunnel.h", "sshdirecttcpiptunnel_p.h", "sshdirecttcpiptunnel.cpp",
+        "sshremoteprocessrunner.cpp", "sshremoteprocessrunner.h",
+        "sshsendfacility.cpp", "sshsendfacility_p.h",
+        "sshkeypasswordretriever.cpp",
+        "sshkeygenerator.cpp", "sshkeygenerator.h",
+        "sshkeycreationdialog.cpp", "sshkeycreationdialog.h", "sshkeycreationdialog.ui",
+        "sftpfilesystemmodel.cpp", "sftpfilesystemmodel.h",
+        "sshincomingpacket_p.h", "sshincomingpacket.cpp",
+        "ssherrors.h",
+        "sshexception_p.h",
+        "sshpseudoterminal.h",
+        "sshbotanconversions_p.h"
+    ]
+
+    ProductModule {
+        Depends { name: "cpp" }
+        Depends { name: "Qt"; submodules: ["widgets", "network"] }
+        cpp.includePaths: [".."]
+    }
+}

+ 7 - 0
src/tool/serverctrl/qssh/qssh.qrc

@@ -0,0 +1,7 @@
+<RCC>
+    <qresource prefix="/ssh">
+        <file>images/dir.png</file>
+        <file>images/help.png</file>
+        <file>images/unknownfile.png</file>
+    </qresource>
+</RCC>

+ 1175 - 0
src/tool/serverctrl/qssh/sftpchannel.cpp

@@ -0,0 +1,1175 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sftpchannel.h"
+#include "sftpchannel_p.h"
+
+#include "sftpdefs.h"
+#include "sshexception_p.h"
+#include "sshincomingpacket_p.h"
+#include "sshlogging_p.h"
+#include "sshsendfacility_p.h"
+
+#include <QDir>
+#include <QFile>
+
+namespace QSsh {
+namespace Internal {
+
+namespace {
+    const quint32 ProtocolVersion = 3;
+
+    QString errorMessage(const QString &serverMessage,
+        const QString &alternativeMessage)
+    {
+        return serverMessage.isEmpty() ? alternativeMessage : serverMessage;
+    }
+
+    QString errorMessage(const SftpStatusResponse &response,
+        const QString &alternativeMessage)
+    {
+        return response.status == SSH_FX_OK ? QString()
+            : errorMessage(response.errorString, alternativeMessage);
+    }
+
+    bool openFile(QFile *localFile, SftpOverwriteMode mode)
+    {
+        if (mode == SftpSkipExisting && localFile->exists())
+            return false;
+
+        QIODevice::OpenMode openMode = QIODevice::WriteOnly;
+        if (mode == SftpOverwriteExisting)
+            openMode |= QIODevice::Truncate;
+        else if (mode == SftpAppendToExisting)
+            openMode |= QIODevice::Append;
+
+        return localFile->open(openMode);
+    }
+
+    SftpError sftpStatusToError(const SftpStatusCode status)
+    {
+        switch (status) {
+        case SSH_FX_OK:
+            return SftpError::NoError;
+        case SSH_FX_EOF:
+            return SftpError::EndOfFile;
+        case SSH_FX_NO_SUCH_FILE:
+            return SftpError::FileNotFound;
+        case SSH_FX_PERMISSION_DENIED:
+            return SftpError::PermissionDenied;
+        case SSH_FX_BAD_MESSAGE:
+            return SftpError::BadMessage;
+        case SSH_FX_NO_CONNECTION:
+            return SftpError::NoConnection;
+        case SSH_FX_CONNECTION_LOST:
+            return SftpError::ConnectionLost;
+        case SSH_FX_OP_UNSUPPORTED:
+            return SftpError::UnsupportedOperation;
+        case SSH_FX_FAILURE:
+        default:
+            return SftpError::GenericFailure;
+        }
+    }
+
+} // anonymous namespace
+} // namespace Internal
+
+//--------------------------------------------------------------------------------------------------
+// SftpChannel
+//--------------------------------------------------------------------------------------------------
+
+SftpChannel::SftpChannel(quint32 channelId,
+    Internal::SshSendFacility &sendFacility)
+    : d(new Internal::SftpChannelPrivate(channelId, sendFacility, this))
+{
+    connect(d, &Internal::SftpChannelPrivate::initialized,
+            this, &SftpChannel::initialized, Qt::QueuedConnection);
+    connect(d, &Internal::SftpChannelPrivate::channelError,
+            this, &SftpChannel::channelError, Qt::QueuedConnection);
+    connect(d, &Internal::SftpChannelPrivate::dataAvailable,
+            this, &SftpChannel::dataAvailable, Qt::QueuedConnection);
+    connect(d, &Internal::SftpChannelPrivate::fileInfoAvailable,
+            this, &SftpChannel::fileInfoAvailable, Qt::QueuedConnection);
+    connect(d, &Internal::SftpChannelPrivate::finished,
+            this, &SftpChannel::finished, Qt::QueuedConnection);
+    connect(d, &Internal::SftpChannelPrivate::closed,
+            this, &SftpChannel::closed, Qt::QueuedConnection);
+    connect(d, &Internal::SftpChannelPrivate::transferProgress,
+            this, &SftpChannel::transferProgress, Qt::QueuedConnection);
+}
+
+SftpChannel::State SftpChannel::state() const
+{
+    switch (d->channelState()) {
+    case Internal::AbstractSshChannel::Inactive:
+        return Uninitialized;
+    case Internal::AbstractSshChannel::SessionRequested:
+        return Initializing;
+    case Internal::AbstractSshChannel::CloseRequested:
+        return Closing;
+    case Internal::AbstractSshChannel::Closed:
+        return Closed;
+    case Internal::AbstractSshChannel::SessionEstablished:
+        return d->m_sftpState == Internal::SftpChannelPrivate::Initialized
+            ? Initialized : Initializing;
+    default:
+        Q_ASSERT(!"Oh no, we forgot to handle a channel state!");
+        return Closed; // For the compiler.
+    }
+}
+
+void SftpChannel::initialize()
+{
+    d->requestSessionStart();
+    d->m_sftpState = Internal::SftpChannelPrivate::SubsystemRequested;
+}
+
+void SftpChannel::closeChannel()
+{
+    d->closeChannel();
+}
+
+SftpJobId SftpChannel::statFile(const QString &path)
+{
+    return d->createJob(Internal::SftpStatFile::Ptr(
+        new Internal::SftpStatFile(++d->m_nextJobId, path)));
+}
+
+SftpJobId SftpChannel::listDirectory(const QString &path)
+{
+    return d->createJob(Internal::SftpListDir::Ptr(
+        new Internal::SftpListDir(++d->m_nextJobId, path)));
+}
+
+SftpJobId SftpChannel::createDirectory(const QString &path)
+{
+    return d->createJob(Internal::SftpMakeDir::Ptr(
+        new Internal::SftpMakeDir(++d->m_nextJobId, path)));
+}
+
+SftpJobId SftpChannel::removeDirectory(const QString &path)
+{
+    return d->createJob(Internal::SftpRmDir::Ptr(
+        new Internal::SftpRmDir(++d->m_nextJobId, path)));
+}
+
+SftpJobId SftpChannel::removeFile(const QString &path)
+{
+    return d->createJob(Internal::SftpRm::Ptr(
+        new Internal::SftpRm(++d->m_nextJobId, path)));
+}
+
+SftpJobId SftpChannel::renameFileOrDirectory(const QString &oldPath,
+    const QString &newPath)
+{
+    return d->createJob(Internal::SftpRename::Ptr(
+        new Internal::SftpRename(++d->m_nextJobId, oldPath, newPath)));
+}
+
+SftpJobId SftpChannel::createLink(const QString &filePath, const QString &target)
+{
+    return d->createJob(Internal::SftpCreateLink::Ptr(
+        new Internal::SftpCreateLink(++d->m_nextJobId, filePath, target)));
+}
+
+SftpJobId SftpChannel::createFile(const QString &path, SftpOverwriteMode mode)
+{
+    return d->createJob(Internal::SftpCreateFile::Ptr(
+        new Internal::SftpCreateFile(++d->m_nextJobId, path, mode)));
+}
+
+SftpJobId SftpChannel::uploadFile(QSharedPointer<QIODevice> device,
+    const QString &remoteFilePath, SftpOverwriteMode mode)
+{
+    if (!device->isOpen() && !device->open(QIODevice::ReadOnly))
+        return SftpInvalidJob;
+    return d->createJob(Internal::SftpUploadFile::Ptr(
+        new Internal::SftpUploadFile(++d->m_nextJobId, remoteFilePath, device, mode)));
+}
+
+SftpJobId SftpChannel::uploadFile(const QString &localFilePath,
+    const QString &remoteFilePath, SftpOverwriteMode mode)
+{
+    QSharedPointer<QFile> localFile(new QFile(localFilePath));
+    if (!localFile->open(QIODevice::ReadOnly))
+        return SftpInvalidJob;
+    return d->createJob(Internal::SftpUploadFile::Ptr(
+        new Internal::SftpUploadFile(++d->m_nextJobId, remoteFilePath, localFile, mode)));
+}
+
+SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath,
+    const QString &localFilePath, SftpOverwriteMode mode)
+{
+    QSharedPointer<QFile> localFile(new QFile(localFilePath));
+    return d->createJob(Internal::SftpDownload::Ptr(
+        new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, localFile, mode)));
+}
+
+SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath, QSharedPointer<QIODevice> device)
+{
+    return d->createJob(Internal::SftpDownload::Ptr(
+        new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, device, SftpOverwriteExisting)));
+}
+
+SftpJobId SftpChannel::uploadDir(const QString &localDirPath,
+    const QString &remoteParentDirPath)
+{
+    if (state() != Initialized)
+        return SftpInvalidJob;
+    const QDir localDir(localDirPath);
+    if (!localDir.exists() || !localDir.isReadable())
+        return SftpInvalidJob;
+    const Internal::SftpUploadDir::Ptr uploadDirOp(
+        new Internal::SftpUploadDir(++d->m_nextJobId));
+    const QString remoteDirPath
+        = remoteParentDirPath + u'/' + localDir.dirName();
+    const Internal::SftpMakeDir::Ptr mkdirOp(
+        new Internal::SftpMakeDir(++d->m_nextJobId, remoteDirPath, uploadDirOp));
+    uploadDirOp->mkdirsInProgress.insert(mkdirOp,
+        Internal::SftpUploadDir::Dir(localDirPath, remoteDirPath));
+    d->createJob(mkdirOp);
+    return uploadDirOp->jobId;
+}
+
+SftpJobId SftpChannel::downloadDir(const QString &remoteDirPath,
+    const QString &localDirPath, SftpOverwriteMode mode)
+{
+    if (state() != Initialized)
+        return SftpInvalidJob;
+    if (!QDir().mkpath(localDirPath))
+        return SftpInvalidJob;
+    const Internal::SftpDownloadDir::Ptr downloadDirOp(
+        new Internal::SftpDownloadDir(++d->m_nextJobId, mode));
+    const Internal::SftpListDir::Ptr lsdirOp(
+        new Internal::SftpListDir(++d->m_nextJobId, remoteDirPath, downloadDirOp));
+    downloadDirOp->lsdirsInProgress.insert(lsdirOp,
+       Internal::SftpDownloadDir::Dir(localDirPath, remoteDirPath));
+    d->createJob(lsdirOp);
+    return downloadDirOp->jobId;
+}
+
+SftpChannel::~SftpChannel()
+{
+    delete d;
+}
+
+//--------------------------------------------------------------------------------------------------
+// SftpChannelPrivate
+//--------------------------------------------------------------------------------------------------
+
+namespace Internal {
+
+SftpChannelPrivate::SftpChannelPrivate(quint32 channelId,
+    SshSendFacility &sendFacility, SftpChannel *sftp)
+    : AbstractSshChannel(channelId, sendFacility),
+      m_nextJobId(0), m_sftpState(Inactive), m_sftp(sftp)
+{
+}
+
+SftpJobId SftpChannelPrivate::createJob(const AbstractSftpOperation::Ptr &job)
+{
+   if (m_sftp->state() != SftpChannel::Initialized)
+       return SftpInvalidJob;
+   m_jobs.insert(job->jobId, job);
+   sendData(job->initialPacket(m_outgoingPacket).rawData());
+   return job->jobId;
+}
+
+void SftpChannelPrivate::handleChannelSuccess()
+{
+    if (channelState() == CloseRequested)
+        return;
+    qCDebug(sshLog, "sftp subsystem initialized");
+    sendData(m_outgoingPacket.generateInit(ProtocolVersion).rawData());
+    m_sftpState = InitSent;
+}
+
+void SftpChannelPrivate::handleChannelFailure()
+{
+    if (channelState() == CloseRequested)
+        return;
+
+    if (m_sftpState != SubsystemRequested) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_MSG_CHANNEL_FAILURE packet.");
+    }
+    emit channelError(tr("Server could not start SFTP subsystem."));
+    closeChannel();
+}
+
+void SftpChannelPrivate::handleChannelDataInternal(const QByteArray &data)
+{
+    if (channelState() == CloseRequested)
+        return;
+
+    m_incomingData += data;
+    m_incomingPacket.consumeData(m_incomingData);
+    while (m_incomingPacket.isComplete()) {
+        handleCurrentPacket();
+        m_incomingPacket.clear();
+        m_incomingPacket.consumeData(m_incomingData);
+    }
+}
+
+void SftpChannelPrivate::handleChannelExtendedDataInternal(quint32 type,
+    const QByteArray &data)
+{
+    qCWarning(sshLog, "Unexpected extended data '%s' of type %d on SFTP channel.",
+              data.data(), type);
+}
+
+void SftpChannelPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
+{
+    qCDebug(sshLog, "Remote SFTP service exited with exit code %d", exitStatus.exitStatus);
+
+    if (channelState() == CloseRequested || channelState() == Closed)
+        return;
+
+    emit channelError(tr("The SFTP server finished unexpectedly with exit code %1.")
+                      .arg(exitStatus.exitStatus));
+
+    // Note: According to the specs, the server must close the channel after this happens,
+    // but OpenSSH doesn't do that, so we need to initiate the closing procedure ourselves.
+    closeChannel();
+}
+
+void SftpChannelPrivate::handleExitSignal(const SshChannelExitSignal &signal)
+{
+    emit channelError(tr("The SFTP server crashed: %1.").arg(signal.error));
+    closeChannel(); // See above.
+}
+
+void SftpChannelPrivate::handleCurrentPacket()
+{
+    qCDebug(sshLog, "Handling SFTP packet of type %d", m_incomingPacket.type());
+    switch (m_incomingPacket.type()) {
+    case SSH_FXP_VERSION:
+        handleServerVersion();
+        break;
+    case SSH_FXP_HANDLE:
+        handleHandle();
+        break;
+    case SSH_FXP_NAME:
+        handleName();
+        break;
+    case SSH_FXP_STATUS:
+        handleStatus();
+        break;
+    case SSH_FXP_DATA:
+        handleReadData();
+        break;
+    case SSH_FXP_ATTRS:
+        handleAttrs();
+        break;
+    default:
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected packet.",
+            tr("Unexpected packet of type %1.").arg(m_incomingPacket.type()));
+    }
+}
+
+void SftpChannelPrivate::handleServerVersion()
+{
+    checkChannelActive();
+    if (m_sftpState != InitSent) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_FXP_VERSION packet.");
+    }
+
+    qCDebug(sshLog, "sftp init received");
+    const quint32 serverVersion = m_incomingPacket.extractServerVersion();
+    if (serverVersion != ProtocolVersion) {
+        emit channelError(tr("Protocol version mismatch: Expected %1, got %2")
+            .arg(serverVersion).arg(ProtocolVersion));
+        closeChannel();
+    } else {
+        m_sftpState = Initialized;
+        emit initialized();
+    }
+}
+
+void SftpChannelPrivate::handleHandle()
+{
+    const SftpHandleResponse &response = m_incomingPacket.asHandleResponse();
+    JobMap::Iterator it = lookupJob(response.requestId);
+    const QSharedPointer<AbstractSftpOperationWithHandle> job
+        = it.value().dynamicCast<AbstractSftpOperationWithHandle>();
+    if (job.isNull()) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_FXP_HANDLE packet.");
+    }
+    if (job->state != AbstractSftpOperationWithHandle::OpenRequested) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_FXP_HANDLE packet.");
+    }
+    job->remoteHandle = response.handle;
+    job->state = AbstractSftpOperationWithHandle::Open;
+
+    switch (it.value()->type()) {
+    case AbstractSftpOperation::ListDir:
+        handleLsHandle(it);
+        break;
+    case AbstractSftpOperation::CreateFile:
+        handleCreateFileHandle(it);
+        break;
+    case AbstractSftpOperation::Download:
+        handleGetHandle(it);
+        break;
+    case AbstractSftpOperation::UploadFile:
+        handlePutHandle(it);
+        break;
+    default:
+        Q_ASSERT(!"Oh no, I forgot to handle an SFTP operation type!");
+    }
+}
+
+void SftpChannelPrivate::handleLsHandle(JobMap::Iterator it)
+{
+    SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
+    sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
+        op->jobId).rawData());
+}
+
+void SftpChannelPrivate::handleCreateFileHandle(JobMap::Iterator it)
+{
+    SftpCreateFile::Ptr op = it.value().staticCast<SftpCreateFile>();
+    sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
+        op->jobId).rawData());
+}
+
+void SftpChannelPrivate::handleGetHandle(JobMap::Iterator it)
+{
+    SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
+    sendData(m_outgoingPacket.generateFstat(op->remoteHandle,
+        op->jobId).rawData());
+    op->statRequested = true;
+}
+
+void SftpChannelPrivate::handlePutHandle(JobMap::Iterator it)
+{
+    SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>();
+    if (op->parentJob && op->parentJob->hasError)
+        sendTransferCloseHandle(op, it.key());
+
+    // OpenSSH does not implement the RFC's append functionality, so we
+    // have to emulate it.
+    if (op->mode == SftpAppendToExisting) {
+        sendData(m_outgoingPacket.generateFstat(op->remoteHandle,
+            op->jobId).rawData());
+        op->statRequested = true;
+    } else {
+        spawnWriteRequests(it);
+    }
+}
+
+void SftpChannelPrivate::handleStatus()
+{
+    const SftpStatusResponse &response = m_incomingPacket.asStatusResponse();
+    qCDebug(sshLog, "%s: status = %d", Q_FUNC_INFO, response.status);
+    JobMap::Iterator it = lookupJob(response.requestId);
+    switch (it.value()->type()) {
+    case AbstractSftpOperation::ListDir:
+        handleLsStatus(it, response);
+        break;
+    case AbstractSftpOperation::Download:
+        handleGetStatus(it, response);
+        break;
+    case AbstractSftpOperation::UploadFile:
+        handlePutStatus(it, response);
+        break;
+    case AbstractSftpOperation::MakeDir:
+        handleMkdirStatus(it, response);
+        break;
+    case AbstractSftpOperation::StatFile:
+    case AbstractSftpOperation::RmDir:
+    case AbstractSftpOperation::Rm:
+    case AbstractSftpOperation::Rename:
+    case AbstractSftpOperation::CreateFile:
+    case AbstractSftpOperation::CreateLink:
+        handleStatusGeneric(it, response);
+        break;
+    }
+}
+
+void SftpChannelPrivate::handleStatusGeneric(JobMap::Iterator it,
+    const SftpStatusResponse &response)
+{
+    AbstractSftpOperation::Ptr op = it.value();
+    const QString error = errorMessage(response, tr("Unknown error."));
+    emit finished(op->jobId, sftpStatusToError(response.status), error);
+    m_jobs.erase(it);
+}
+
+void SftpChannelPrivate::handleMkdirStatus(JobMap::Iterator it,
+    const SftpStatusResponse &response)
+{
+    SftpMakeDir::Ptr op = it.value().staticCast<SftpMakeDir>();
+    QSharedPointer<SftpUploadDir> parentJob = op->parentJob;
+    if (parentJob == SftpUploadDir::Ptr()) {
+        handleStatusGeneric(it, response);
+        return;
+    }
+    if (parentJob->hasError) {
+        m_jobs.erase(it);
+        return;
+    }
+
+    typedef QMap<SftpMakeDir::Ptr, SftpUploadDir::Dir>::Iterator DirIt;
+    DirIt dirIt = parentJob->mkdirsInProgress.find(op);
+    Q_ASSERT(dirIt != parentJob->mkdirsInProgress.end());
+    const QString &remoteDir = dirIt.value().remoteDir;
+    if (response.status == SSH_FX_OK) {
+        emit dataAvailable(parentJob->jobId,
+            tr("Created remote directory \"%1\".").arg(remoteDir));
+    } else if (response.status == SSH_FX_FAILURE) {
+        emit dataAvailable(parentJob->jobId,
+            tr("Remote directory \"%1\" already exists.").arg(remoteDir));
+    } else {
+        parentJob->setError();
+        emit finished(parentJob->jobId,
+            sftpStatusToError(response.status),
+            tr("Error creating directory \"%1\": %2")
+            .arg(remoteDir, response.errorString));
+        m_jobs.erase(it);
+        return;
+    }
+
+    QDir localDir(dirIt.value().localDir);
+    const QFileInfoList &dirInfos
+        = localDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+    for (const QFileInfo &dirInfo : dirInfos) {
+        const QString remoteSubDir = remoteDir + u'/' + dirInfo.fileName();
+        const SftpMakeDir::Ptr mkdirOp(
+            new SftpMakeDir(++m_nextJobId, remoteSubDir, parentJob));
+        parentJob->mkdirsInProgress.insert(mkdirOp,
+            SftpUploadDir::Dir(dirInfo.absoluteFilePath(), remoteSubDir));
+        createJob(mkdirOp);
+    }
+
+    const QFileInfoList &fileInfos = localDir.entryInfoList(QDir::Files);
+    for (const QFileInfo &fileInfo : fileInfos) {
+        QSharedPointer<QFile> localFile(new QFile(fileInfo.absoluteFilePath()));
+        if (!localFile->open(QIODevice::ReadOnly)) {
+            parentJob->setError();
+            emit finished(parentJob->jobId,
+                sftpStatusToError(response.status),
+                tr("Could not open local file \"%1\": %2")
+                .arg(fileInfo.absoluteFilePath(), localFile->errorString()));
+            m_jobs.erase(it);
+            return;
+        }
+
+        const QString remoteFilePath = remoteDir + u'/' + fileInfo.fileName();
+        SftpUploadFile::Ptr uploadFileOp(new SftpUploadFile(++m_nextJobId,
+            remoteFilePath, localFile, SftpOverwriteExisting, parentJob));
+        createJob(uploadFileOp);
+        parentJob->uploadsInProgress.append(uploadFileOp);
+    }
+
+    parentJob->mkdirsInProgress.erase(dirIt);
+    if (parentJob->mkdirsInProgress.isEmpty()
+        && parentJob->uploadsInProgress.isEmpty())
+        emit finished(parentJob->jobId);
+    m_jobs.erase(it);
+}
+
+void SftpChannelPrivate::handleLsStatus(JobMap::Iterator it,
+    const SftpStatusResponse &response)
+{
+    SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
+
+    if (op->parentJob && op->parentJob->hasError) {
+        m_jobs.erase(it);
+        return;
+    }
+
+    switch (op->state) {
+    case SftpListDir::OpenRequested:
+        reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
+            tr("Remote directory could not be opened for reading.")));
+        m_jobs.erase(it);
+        break;
+    case SftpListDir::Open:
+        if (response.status != SSH_FX_EOF)
+            reportRequestError(op,
+                sftpStatusToError(response.status),
+                errorMessage(response.errorString,
+                tr("Failed to list remote directory contents.")));
+        op->state = SftpListDir::CloseRequested;
+        sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
+            op->jobId).rawData());
+        break;
+    case SftpListDir::CloseRequested:
+        if (op->hasError || (op->parentJob && op->parentJob->hasError)) {
+            m_jobs.erase(it);
+            return;
+        }
+
+        {
+            const QString error = errorMessage(response,
+                tr("Failed to close remote directory."));
+
+            if (op->parentJob) {
+                if (!error.isEmpty()) {
+                    op->parentJob->setError();
+                }
+                if (op->parentJob->hasError) {
+                    emit finished(op->parentJob->jobId, sftpStatusToError(response.status), error);
+                } else {
+                    op->parentJob->lsdirsInProgress.remove(op);
+                    if (op->parentJob->lsdirsInProgress.isEmpty() &&
+                        op->parentJob->downloadsInProgress.isEmpty()) {
+                        emit finished(op->parentJob->jobId);
+                    }
+                }
+            } else {
+                emit finished(op->jobId, sftpStatusToError(response.status), error);
+            }
+        }
+        m_jobs.erase(it);
+        break;
+    default:
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_FXP_STATUS packet.");
+    }
+}
+
+void SftpChannelPrivate::handleGetStatus(JobMap::Iterator it,
+    const SftpStatusResponse &response)
+{
+    SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
+
+    if (op->parentJob && op->parentJob->hasError) {
+        m_jobs.erase(it);
+        return;
+    }
+
+    switch (op->state) {
+    case SftpDownload::OpenRequested:
+        reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
+            tr("Failed to open remote file for reading.")));
+        m_jobs.erase(it);
+        break;
+    case SftpDownload::Open:
+        if (op->statRequested) {
+            reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
+                tr("Failed to retrieve information on the remote file ('stat' failed).")));
+            sendTransferCloseHandle(op, response.requestId);
+        } else {
+            if ((response.status != SSH_FX_EOF || response.requestId != op->eofId)
+                && !op->hasError)
+                reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
+                    tr("Failed to read remote file.")));
+            finishTransferRequest(it);
+        }
+        break;
+    case SftpDownload::CloseRequested:
+        Q_ASSERT(op->inFlightCount == 1);
+        if (!op->hasError) {
+            if (response.status == SSH_FX_OK) {
+                if (op->parentJob) {
+                    op->parentJob->downloadsInProgress.removeOne(op);
+                    if (op->parentJob->lsdirsInProgress.isEmpty()
+                        && op->parentJob->downloadsInProgress.isEmpty())
+                        emit finished(op->parentJob->jobId);
+                } else {
+                    emit finished(op->jobId);
+                }
+            } else {
+                const QString error = errorMessage(response.errorString,
+                    tr("Failed to close remote file."));
+                reportRequestError(op, sftpStatusToError(response.status), error);
+            }
+        }
+        removeTransferRequest(it);
+        break;
+    default:
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_FXP_STATUS packet.");
+    }
+}
+
+void SftpChannelPrivate::handlePutStatus(JobMap::Iterator it,
+    const SftpStatusResponse &response)
+{
+    SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
+    switch (job->state) {
+    case SftpUploadFile::OpenRequested: {
+        bool emitError = false;
+        if (job->parentJob) {
+            if (!job->parentJob->hasError) {
+                job->parentJob->setError();
+                emitError = true;
+            }
+        } else {
+            emitError = true;
+        }
+
+        if (emitError) {
+            emit finished(job->jobId,
+                sftpStatusToError(response.status),
+                errorMessage(response.errorString,
+                    tr("Failed to open remote file for writing.")));
+        }
+        m_jobs.erase(it);
+        break;
+    }
+    case SftpUploadFile::Open:
+        if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
+            job->hasError = true;
+            finishTransferRequest(it);
+            return;
+        }
+
+        if (response.status == SSH_FX_OK) {
+            sendWriteRequest(it);
+        } else {
+            if (job->parentJob)
+                job->parentJob->setError();
+            reportRequestError(job, sftpStatusToError(response.status), errorMessage(response.errorString,
+                tr("Failed to write remote file.")));
+            finishTransferRequest(it);
+        }
+        break;
+    case SftpUploadFile::CloseRequested:
+        Q_ASSERT(job->inFlightCount == 1);
+        if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
+            m_jobs.erase(it);
+            return;
+        }
+
+        if (response.status == SSH_FX_OK) {
+            if (job->parentJob) {
+                job->parentJob->uploadsInProgress.removeOne(job);
+                if (job->parentJob->mkdirsInProgress.isEmpty()
+                    && job->parentJob->uploadsInProgress.isEmpty())
+                    emit finished(job->parentJob->jobId);
+            } else {
+                emit finished(job->jobId);
+            }
+        } else {
+            const QString error = errorMessage(response.errorString,
+                tr("Failed to close remote file."));
+            if (job->parentJob) {
+                job->parentJob->setError();
+                emit finished(job->parentJob->jobId, sftpStatusToError(response.status), error);
+            } else {
+                emit finished(job->jobId, sftpStatusToError(response.status), error);
+            }
+        }
+        m_jobs.erase(it);
+        break;
+    default:
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_FXP_STATUS packet.");
+    }
+}
+
+void SftpChannelPrivate::handleName()
+{
+    const SftpNameResponse &response = m_incomingPacket.asNameResponse();
+    JobMap::Iterator it = lookupJob(response.requestId);
+    switch (it.value()->type()) {
+    case AbstractSftpOperation::ListDir: {
+        SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
+        if (op->state != SftpListDir::Open) {
+            throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+                "Unexpected SSH_FXP_NAME packet.");
+        }
+
+        QList<SftpFileInfo> fileInfoList;
+        for (int i = 0; i < response.files.count(); ++i) {
+            const SftpFile &file = response.files.at(i);
+
+            SftpFileInfo fileInfo;
+            fileInfo.name = file.fileName;
+            attributesToFileInfo(file.attributes, fileInfo);
+            fileInfoList << fileInfo;
+        }
+
+        if (op->parentJob) {
+            handleDownloadDir(op, fileInfoList);
+        } else {
+            emit fileInfoAvailable(op->jobId, fileInfoList);
+        }
+
+        sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
+            op->jobId).rawData());
+        break;
+    }
+    default:
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_FXP_NAME packet.");
+    }
+}
+
+void SftpChannelPrivate::handleReadData()
+{
+    const SftpDataResponse &response = m_incomingPacket.asDataResponse();
+    JobMap::Iterator it = lookupJob(response.requestId);
+    if (it.value()->type() != AbstractSftpOperation::Download) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_FXP_DATA packet.");
+    }
+
+    SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
+    if (op->hasError) {
+        finishTransferRequest(it);
+        return;
+    }
+
+    if (!op->localFile->isOpen()) {
+        QFile *fileDevice = qobject_cast<QFile*>(op->localFile.data());
+        if (fileDevice){
+            if (!Internal::openFile(fileDevice, op->mode)) {
+                reportRequestError(op, SftpError::GenericFailure, tr("Cannot open file ") + fileDevice->fileName());
+                finishTransferRequest(it);
+                return;
+            }
+        } else {
+            reportRequestError(op, SftpError::GenericFailure, tr("File to upload is not open"));
+            finishTransferRequest(it);
+            return;
+        }
+    }
+
+    if (!op->localFile->seek(op->offsets[response.requestId])) {
+        reportRequestError(op, SftpError::GenericFailure, op->localFile->errorString());
+        finishTransferRequest(it);
+        return;
+    }
+
+    if (op->localFile->write(response.data) != response.data.size()) {
+        reportRequestError(op, SftpError::GenericFailure, op->localFile->errorString());
+        finishTransferRequest(it);
+        return;
+    }
+
+    emit transferProgress(op->jobId, op->localFile->pos(), op->fileSize);
+
+    if (op->offset >= op->fileSize && op->fileSize != 0)
+        finishTransferRequest(it);
+    else
+        sendReadRequest(op, response.requestId);
+}
+
+void SftpChannelPrivate::handleAttrs()
+{
+    const SftpAttrsResponse &response = m_incomingPacket.asAttrsResponse();
+    JobMap::Iterator it = lookupJob(response.requestId);
+
+    SftpStatFile::Ptr statOp = it.value().dynamicCast<SftpStatFile>();
+    if (statOp) {
+        SftpFileInfo fileInfo;
+        fileInfo.name = QFileInfo(statOp->path).fileName();
+        attributesToFileInfo(response.attrs, fileInfo);
+        emit fileInfoAvailable(it.key(), QList<SftpFileInfo>() << fileInfo);
+        emit finished(it.key());
+        m_jobs.erase(it);
+        return;
+    }
+
+    AbstractSftpTransfer::Ptr transfer
+        = it.value().dynamicCast<AbstractSftpTransfer>();
+    if (!transfer || transfer->state != AbstractSftpTransfer::Open
+        || !transfer->statRequested) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_FXP_ATTRS packet.");
+    }
+    Q_ASSERT(transfer->type() == AbstractSftpOperation::UploadFile
+        || transfer->type() == AbstractSftpOperation::Download);
+
+    if (transfer->type() == AbstractSftpOperation::Download) {
+        SftpDownload::Ptr op = transfer.staticCast<SftpDownload>();
+        if (response.attrs.sizePresent) {
+            op->fileSize = response.attrs.size;
+        } else {
+            op->fileSize = 0;
+            op->eofId = op->jobId;
+        }
+        op->statRequested = false;
+        emit transferProgress(op->jobId, op->offset, op->fileSize);
+        spawnReadRequests(op);
+    } else {
+        SftpUploadFile::Ptr op = transfer.staticCast<SftpUploadFile>();
+        if (op->parentJob && op->parentJob->hasError) {
+            op->hasError = true;
+            sendTransferCloseHandle(op, op->jobId);
+            return;
+        }
+
+        if (response.attrs.sizePresent) {
+            op->offset = response.attrs.size;
+            emit transferProgress(op->jobId, op->offset, op->fileSize);
+            spawnWriteRequests(it);
+        } else {
+            if (op->parentJob)
+                op->parentJob->setError();
+            reportRequestError(op, SftpError::UnsupportedOperation, tr("Cannot append to remote file: "
+                "Server does not support the file size attribute."));
+            sendTransferCloseHandle(op, op->jobId);
+        }
+    }
+}
+
+void SftpChannelPrivate::handleDownloadDir(SftpListDir::Ptr op,
+    const QList<SftpFileInfo> &fileInfoList)
+{
+    if (op->parentJob->hasError) {
+        return;
+    }
+
+    for (SftpFileInfo fileInfo : fileInfoList) {
+        Internal::SftpDownloadDir::Dir dir = op->parentJob->lsdirsInProgress[op];
+        QString fullPathRemote = QDir(dir.remoteDir).path() + u'/' + fileInfo.name;
+        QString fullPathLocal = QDir(dir.localDir).path() + u'/' + fileInfo.name;
+
+        if (fileInfo.type == FileTypeRegular) {
+            QSharedPointer<QFile> localFile(new QFile(fullPathLocal));
+            Internal::SftpDownload::Ptr downloadJob = Internal::SftpDownload::Ptr(
+                new Internal::SftpDownload(++m_nextJobId, fullPathRemote, localFile,
+                                           op->parentJob->mode, op->parentJob));
+
+            op->parentJob->downloadsInProgress.append(downloadJob);
+            createJob(downloadJob);
+
+        } else if (fileInfo.type == FileTypeDirectory) {
+            if (fileInfo.name == u"." || fileInfo.name == u"..") {
+                continue;
+            }
+
+            if (!QDir().mkpath(fullPathLocal)) {
+                reportRequestError(op, SftpError::GenericFailure, tr("Cannot create directory ") + fullPathLocal);
+                break;
+            }
+
+            Internal::SftpListDir::Ptr lsdir = Internal::SftpListDir::Ptr(
+                new Internal::SftpListDir(++m_nextJobId, fullPathRemote, op->parentJob));
+
+            op->parentJob->lsdirsInProgress.insert(lsdir,
+                Internal::SftpDownloadDir::Dir(fullPathLocal, fullPathRemote));
+            createJob(lsdir);
+
+        } else {
+            // andres.pagliano TODO handle?
+        }
+    }
+}
+
+SftpChannelPrivate::JobMap::Iterator SftpChannelPrivate::lookupJob(SftpJobId id)
+{
+    JobMap::Iterator it = m_jobs.find(id);
+    if (it == m_jobs.end()) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid request id in SFTP packet.");
+    }
+    return it;
+}
+
+void SftpChannelPrivate::closeHook()
+{
+    for (JobMap::ConstIterator it = m_jobs.constBegin(); it != m_jobs.constEnd(); ++it)
+        emit finished(it.key(), SftpError::EndOfFile, tr("SFTP channel closed unexpectedly."));
+    m_jobs.clear();
+    m_incomingData.clear();
+    m_incomingPacket.clear();
+    emit closed();
+}
+
+void SftpChannelPrivate::handleOpenSuccessInternal()
+{
+    qCDebug(sshLog, "SFTP session started");
+    m_sendFacility.sendSftpPacket(remoteChannel());
+    m_sftpState = SubsystemRequested;
+}
+
+void SftpChannelPrivate::handleOpenFailureInternal(const QString &reason)
+{
+    if (channelState() != SessionRequested) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
+    }
+    emit channelError(tr("Server could not start session: %1").arg(reason));
+}
+
+void SftpChannelPrivate::sendReadRequest(const SftpDownload::Ptr &job,
+    quint32 requestId)
+{
+    Q_ASSERT(job->eofId == SftpInvalidJob);
+    sendData(m_outgoingPacket.generateReadFile(job->remoteHandle, job->offset,
+        AbstractSftpPacket::MaxDataSize, requestId).rawData());
+    job->offsets[requestId] = job->offset;
+    job->offset += AbstractSftpPacket::MaxDataSize;
+    if (job->offset >= job->fileSize)
+        job->eofId = requestId;
+}
+
+void SftpChannelPrivate::reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job,
+    const SftpError errorType,
+    const QString &error)
+{
+    // andres.pagliano TODO refactor
+
+    // Report list error during download dir
+    SftpListDir::Ptr lsjob = job.dynamicCast<SftpListDir>();
+    if (!lsjob.isNull() && lsjob->parentJob) {
+        if (!lsjob->parentJob->hasError) {
+            emit finished(lsjob->parentJob->jobId, errorType, error);
+            lsjob->parentJob->hasError = true;
+        }
+    } else {
+        // Report download error during recursive download dir
+        SftpDownload::Ptr djob = job.dynamicCast<SftpDownload>();
+        if (!djob.isNull() && djob->parentJob) {
+            if (!djob->parentJob->hasError) {
+                emit finished(djob->parentJob->jobId, errorType, error);
+                djob->parentJob->hasError = true;
+            }
+        } else {
+            // Other error
+            emit finished(job->jobId, errorType, error);
+        }
+    }
+    job->hasError = true;
+}
+
+void SftpChannelPrivate::finishTransferRequest(JobMap::Iterator it)
+{
+    AbstractSftpTransfer::Ptr job = it.value().staticCast<AbstractSftpTransfer>();
+    if (job->inFlightCount == 1)
+        sendTransferCloseHandle(job, it.key());
+    else
+        removeTransferRequest(it);
+}
+
+void SftpChannelPrivate::sendTransferCloseHandle(const AbstractSftpTransfer::Ptr &job,
+    quint32 requestId)
+{
+    sendData(m_outgoingPacket.generateCloseHandle(job->remoteHandle,
+       requestId).rawData());
+    job->state = SftpDownload::CloseRequested;
+}
+
+void SftpChannelPrivate::attributesToFileInfo(const SftpFileAttributes &attributes,
+    SftpFileInfo &fileInfo) const
+{
+    if (attributes.sizePresent) {
+        fileInfo.sizeValid = true;
+        fileInfo.size = attributes.size;
+    }
+    if (attributes.permissionsPresent) {
+        if (attributes.permissions & 0x8000) // S_IFREG
+            fileInfo.type = FileTypeRegular;
+        else if (attributes.permissions & 0x4000) // S_IFDIR
+            fileInfo.type = FileTypeDirectory;
+        else
+            fileInfo.type = FileTypeOther;
+        fileInfo.permissionsValid = true;
+        fileInfo.permissions = {};
+
+        if (attributes.timesPresent) {
+            fileInfo.atime = attributes.atime;
+            fileInfo.mtime = attributes.mtime;
+            fileInfo.timestampsValid = true;
+        }
+
+        if (attributes.permissions & 00001) // S_IXOTH
+            fileInfo.permissions |= QFile::ExeOther;
+        if (attributes.permissions & 00002) // S_IWOTH
+            fileInfo.permissions |= QFile::WriteOther;
+        if (attributes.permissions & 00004) // S_IROTH
+            fileInfo.permissions |= QFile::ReadOther;
+        if (attributes.permissions & 00010) // S_IXGRP
+            fileInfo.permissions |= QFile::ExeGroup;
+        if (attributes.permissions & 00020) // S_IWGRP
+            fileInfo.permissions |= QFile::WriteGroup;
+        if (attributes.permissions & 00040) // S_IRGRP
+            fileInfo.permissions |= QFile::ReadGroup;
+        if (attributes.permissions & 00100) // S_IXUSR
+            fileInfo.permissions |= QFile::ExeUser | QFile::ExeOwner;
+        if (attributes.permissions & 00200) // S_IWUSR
+            fileInfo.permissions |= QFile::WriteUser | QFile::WriteOwner;
+        if (attributes.permissions & 00400) // S_IRUSR
+            fileInfo.permissions |= QFile::ReadUser | QFile::ReadOwner;
+    }
+}
+
+void SftpChannelPrivate::removeTransferRequest(JobMap::Iterator it)
+{
+    --it.value().staticCast<AbstractSftpTransfer>()->inFlightCount;
+    m_jobs.erase(it);
+}
+
+void SftpChannelPrivate::sendWriteRequest(JobMap::Iterator it)
+{
+    SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
+
+    emit transferProgress(job->jobId, job->localFile->pos(), job->localFile->size());
+
+    QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize);
+
+    QFileDevice *fileDevice = qobject_cast<QFileDevice*>(job->localFile.data());
+    if (fileDevice && fileDevice->error() != QFileDevice::NoError) {
+        if (job->parentJob)
+            job->parentJob->setError();
+        reportRequestError(job, SftpError::GenericFailure, tr("Error reading local file: %1")
+            .arg(job->localFile->errorString()));
+        finishTransferRequest(it);
+    } else if (data.isEmpty()) {
+        finishTransferRequest(it);
+    } else {
+        sendData(m_outgoingPacket.generateWriteFile(job->remoteHandle,
+            job->offset, data, it.key()).rawData());
+        job->offset += AbstractSftpPacket::MaxDataSize;
+    }
+}
+
+void SftpChannelPrivate::spawnWriteRequests(JobMap::Iterator it)
+{
+    SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>();
+    op->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
+    sendWriteRequest(it);
+    for (int i = 1; !op->hasError && i < op->inFlightCount; ++i)
+        sendWriteRequest(m_jobs.insert(++m_nextJobId, op));
+}
+
+void SftpChannelPrivate::spawnReadRequests(const SftpDownload::Ptr &job)
+{
+    job->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
+    sendReadRequest(job, job->jobId);
+    for (int i = 1; i < job->inFlightCount; ++i) {
+        const quint32 requestId = ++m_nextJobId;
+        m_jobs.insert(requestId, job);
+        sendReadRequest(job, requestId);
+    }
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 263 - 0
src/tool/serverctrl/qssh/sftpchannel.h

@@ -0,0 +1,263 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SFTCHANNEL_H
+#define SFTCHANNEL_H
+
+#include "sftpdefs.h"
+
+#include "ssh_global.h"
+
+#include <QByteArray>
+#include <QObject>
+#include <QSharedPointer>
+#include <QString>
+
+namespace QSsh {
+
+namespace Internal {
+class SftpChannelPrivate;
+class SshChannelManager;
+class SshSendFacility;
+} // namespace Internal
+
+
+/*!
+    \class QSsh::SftpChannel
+
+    \brief This class provides SFTP operations.
+
+    Objects are created via SshConnection::createSftpChannel().
+    The channel needs to be initialized with
+    a call to initialize() and is closed via closeChannel(). After closing
+    a channel, no more operations are possible. It cannot be re-opened
+    using initialize(); use SshConnection::createSftpChannel() if you need
+    a new one.
+
+    After the initialized() signal has been emitted, operations can be started.
+    All SFTP operations are asynchronous (non-blocking) and can be in-flight
+    simultaneously (though callers must ensure that concurrently running jobs
+    are independent of each other, e.g. they must not write to the same file).
+    Operations are identified by their job id, which is returned by
+    the respective member function. If the function can right away detect that
+    the operation cannot succeed, it returns SftpInvalidJob. If an error occurs
+    later, the finished() signal is emitted for the respective job with a
+    non-empty error string.
+
+    Note that directory names must not have a trailing slash.
+*/
+
+class QSSH_EXPORT SftpChannel : public QObject
+{
+    Q_OBJECT
+
+    friend class Internal::SftpChannelPrivate;
+    friend class Internal::SshChannelManager;
+public:
+    /// Convenience typedef
+    typedef QSharedPointer<SftpChannel> Ptr;
+
+    /// \see state
+    enum State { Uninitialized, Initializing, Initialized, Closing, Closed };
+
+    /// Current state of this channel
+    State state() const;
+
+    /*!
+     * @brief Makes this channel ready to use.
+     */
+    void initialize();
+
+    /*!
+     * @brief Call this when you are done with the channel.
+     */
+    void closeChannel();
+
+    /*!
+     * \brief Get information about a remote path, file or directory
+     * \param path Remote path to state
+     * \return A unique ID identifying this job
+     */
+    SftpJobId statFile(const QString &path);
+
+    /*!
+     * \brief Get list of contents of a directory
+     * \param dirPath Remote path of directory
+     * \return A unique ID identifying this job
+     */
+    SftpJobId listDirectory(const QString &dirPath);
+
+    /*!
+     * \brief Create remote directory
+     * \param dirPath Remote path of directory
+     * \return A unique ID identifying this job
+     */
+    SftpJobId createDirectory(const QString &dirPath);
+
+    /*!
+     * \brief Remove remote directory
+     * \param dirPath Remote path of directory
+     * \return A unique ID identifying this job
+     */
+    SftpJobId removeDirectory(const QString &dirPath);
+
+    /*!
+     * \brief Remove remote file
+     * \param filePath Remote path of file
+     * \return A unique ID identifying this job
+     */
+    SftpJobId removeFile(const QString &filePath);
+
+    /*!
+     * \brief Rename or move a remote file or directory
+     * \param oldPath Path of existing file or directory
+     * \param newPath New path the file or directory should be available as
+     * \return A unique ID identifying this job
+     */
+    SftpJobId renameFileOrDirectory(const QString &oldPath,
+        const QString &newPath);
+
+    /*!
+     * \brief Create a new empty file.
+     * \param filePath Remote path of the file.
+     * \param mode The behavior if the file already exists.
+     * \return A unique ID identifying this job
+     */
+    SftpJobId createFile(const QString &filePath, SftpOverwriteMode mode);
+
+    /*!
+     * \brief Creates a symbolic link pointing to another file.
+     * \param filePath The path of the symbolic
+     * \param target The path the symbolic link should point to
+     * \return A unique ID identifying this job
+     */
+    SftpJobId createLink(const QString &filePath, const QString &target);
+
+    /*!
+     * \brief Creates a remote file and fills it with data from \a device
+     * \param device If this is not open already it will be opened in \a QIODevice::ReadOnly mode
+     * \param remoteFilePath The path on the server to upload the file to
+     * \param mode #QSsh::SftpOverwriteMode defines the behavior if the file already exists
+     * \return A unique ID identifying this job
+     */
+    SftpJobId uploadFile(QSharedPointer<QIODevice> device,
+        const QString &remoteFilePath, SftpOverwriteMode mode);
+
+    /*!
+     * \brief Uploads a local file to the remote host.
+     * \param localFilePath The local path to an existing file
+     * \param remoteFilePath The remote path the file should be uploaded to
+     * \param mode What it will do if the file already exists
+     * \return A unique ID identifying this job
+     */
+    SftpJobId uploadFile(const QString &localFilePath,
+        const QString &remoteFilePath, SftpOverwriteMode mode);
+
+    /*!
+     * \brief Downloads a remote file to a local path
+     * \param remoteFilePath The remote path to the file to be downloaded
+     * \param localFilePath The local path for where to download the file
+     * \param mode Controls what happens if the local file already exists
+     * \return A unique ID identifying this job
+     */
+    SftpJobId downloadFile(const QString &remoteFilePath,
+        const QString &localFilePath, SftpOverwriteMode mode);
+
+    /*!
+     * \brief Retrieves the contents of a remote file and writes it to \a device
+     * \param remoteFilePath The remote path of the file to retrieve the contents of
+     * \param device The QIODevice to write the data to, this needs to be open in a writable mode
+     * \return A unique ID identifying this job
+     */
+    SftpJobId downloadFile(const QString &remoteFilePath,
+        QSharedPointer<QIODevice> device);
+
+    /*!
+     * \brief Uploads a local directory (recursively) with files to the remote host
+     * \param localDirPath The path to an existing local directory
+     * \param remoteParentDirPath The remote path to upload it to, the name of the local directory will be appended to this
+     * \return A unique ID identifying this job
+     */
+    SftpJobId uploadDir(const QString &localDirPath,
+        const QString &remoteParentDirPath);
+
+    /*!
+     * \brief Downloads a remote directory (recursively) to a local path
+     * \param remoteDirPath The remote path of an existing directory to download
+     * \param localDirPath The local path to download the directory to
+     * \param mode
+     * \return
+     */
+    SftpJobId downloadDir(const QString &remoteDirPath,
+        const QString &localDirPath, SftpOverwriteMode mode);
+
+    ~SftpChannel();
+
+signals:
+    /// Emitted when you can start using the channel
+    void initialized();
+
+    /// Emitted when an error happened
+    void channelError(const QString &reason);
+
+    /// Emitted when the channel has closed for some reason, either an error occured or it was asked for.
+    void closed();
+
+    /// error.isEmpty means it finished successfully
+    void finished(QSsh::SftpJobId job, const SftpError errorType = SftpError::NoError, const QString &error = QString());
+
+    /*!
+     * Continously emitted during data transfer.
+     * Does not emit for each file copied by uploadDir().
+     */
+    void dataAvailable(QSsh::SftpJobId job, const QString &data);
+
+    /*!
+     * This signal is emitted as a result of:
+     *     - statFile() (with the list having exactly one element)
+     *     - listDirectory() (potentially more than once)
+     * It will continously be emitted as data is discovered, not only when the job is done.
+     */
+    void fileInfoAvailable(QSsh::SftpJobId job, const QList<QSsh::SftpFileInfo> &fileInfoList);
+
+    /*!
+     * Emitted during upload or download
+     */
+    void transferProgress(QSsh::SftpJobId job, quint64 progress, quint64 total);
+
+private:
+    SftpChannel(quint32 channelId, Internal::SshSendFacility &sendFacility);
+
+    Internal::SftpChannelPrivate *d;
+};
+
+} // namespace QSsh
+
+#endif // SFTPCHANNEL_H

+ 135 - 0
src/tool/serverctrl/qssh/sftpchannel_p.h

@@ -0,0 +1,135 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SFTCHANNEL_P_H
+#define SFTCHANNEL_P_H
+
+#include "sftpdefs.h"
+#include "sftpincomingpacket_p.h"
+#include "sftpoperation_p.h"
+#include "sftpoutgoingpacket_p.h"
+#include "sshchannel_p.h"
+
+#include <QByteArray>
+#include <QMap>
+
+namespace QSsh {
+class SftpChannel;
+namespace Internal {
+
+class SftpChannelPrivate : public AbstractSshChannel
+{
+    Q_OBJECT
+    friend class QSsh::SftpChannel;
+public:
+    enum SftpState { Inactive, SubsystemRequested, InitSent, Initialized };
+
+signals:
+    void initialized();
+    void channelError(const QString &reason);
+    void closed();
+    void finished(QSsh::SftpJobId job, const SftpError errorType = SftpError::NoError, const QString &error = QString());
+    void dataAvailable(QSsh::SftpJobId job, const QString &data);
+    void fileInfoAvailable(QSsh::SftpJobId job, const QList<QSsh::SftpFileInfo> &fileInfoList);
+    void transferProgress(QSsh::SftpJobId job, quint64 progress, quint64 total);
+
+private:
+    typedef QMap<SftpJobId, AbstractSftpOperation::Ptr> JobMap;
+
+    SftpChannelPrivate(quint32 channelId, SshSendFacility &sendFacility,
+        SftpChannel *sftp);
+    SftpJobId createJob(const AbstractSftpOperation::Ptr &job);
+
+    virtual void handleChannelSuccess();
+    virtual void handleChannelFailure();
+
+    virtual void handleOpenSuccessInternal();
+    virtual void handleOpenFailureInternal(const QString &reason);
+    virtual void handleChannelDataInternal(const QByteArray &data);
+    virtual void handleChannelExtendedDataInternal(quint32 type,
+        const QByteArray &data);
+    virtual void handleExitStatus(const SshChannelExitStatus &exitStatus);
+    virtual void handleExitSignal(const SshChannelExitSignal &signal);
+
+    virtual void closeHook();
+
+    void handleCurrentPacket();
+    void handleServerVersion();
+    void handleHandle();
+    void handleStatus();
+    void handleName();
+    void handleReadData();
+    void handleAttrs();
+
+    void handleDownloadDir(SftpListDir::Ptr op, const QList<SftpFileInfo> & fileInfoList);
+
+    void handleStatusGeneric(JobMap::Iterator it,
+        const SftpStatusResponse &response);
+    void handleMkdirStatus(JobMap::Iterator it,
+        const SftpStatusResponse &response);
+    void handleLsStatus(JobMap::Iterator it,
+        const SftpStatusResponse &response);
+    void handleGetStatus(JobMap::Iterator it,
+        const SftpStatusResponse &response);
+    void handlePutStatus(JobMap::Iterator it,
+        const SftpStatusResponse &response);
+
+    void handleLsHandle(JobMap::Iterator it);
+    void handleCreateFileHandle(JobMap::Iterator it);
+    void handleGetHandle(JobMap::Iterator it);
+    void handlePutHandle(JobMap::Iterator it);
+
+    void spawnReadRequests(const SftpDownload::Ptr &job);
+    void spawnWriteRequests(JobMap::Iterator it);
+    void sendReadRequest(const SftpDownload::Ptr &job, quint32 requestId);
+    void sendWriteRequest(JobMap::Iterator it);
+    void finishTransferRequest(JobMap::Iterator it);
+    void removeTransferRequest(JobMap::Iterator it);
+    void reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job, const SftpError errorType,
+        const QString &error);
+    void sendTransferCloseHandle(const AbstractSftpTransfer::Ptr &job,
+        quint32 requestId);
+
+    void attributesToFileInfo(const SftpFileAttributes &attributes, SftpFileInfo &fileInfo) const;
+
+    JobMap::Iterator lookupJob(SftpJobId id);
+    JobMap m_jobs;
+    SftpOutgoingPacket m_outgoingPacket;
+    SftpIncomingPacket m_incomingPacket;
+    QByteArray m_incomingData;
+    SftpJobId m_nextJobId;
+    SftpState m_sftpState;
+    SftpChannel *m_sftp;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SFTPCHANNEL_P_H

+ 33 - 0
src/tool/serverctrl/qssh/sftpdefs.cpp

@@ -0,0 +1,33 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sftpdefs.h"
+
+namespace QSsh { const SftpJobId SftpInvalidJob = 0; }

+ 119 - 0
src/tool/serverctrl/qssh/sftpdefs.h

@@ -0,0 +1,119 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SFTPDEFS_H
+#define SFTPDEFS_H
+
+#include "ssh_global.h"
+
+#include <QFile>
+#include <QString>
+
+/*!
+ * \namespace QSsh
+ * \brief The namespace used for the entire library
+ */
+namespace QSsh {
+
+
+/*!
+ *\brief Unique ID used for tracking individual jobs.
+ */
+typedef quint32 SftpJobId;
+
+/*!
+    Special ID representing an invalid job, e. g. if a requested job could not be started.
+*/
+QSSH_EXPORT extern const SftpJobId SftpInvalidJob;
+
+
+/*!
+ * \brief The behavior when uploading a file and the remote path already exists
+ */
+enum SftpOverwriteMode {
+    /*! Overwrite any existing files */
+    SftpOverwriteExisting,
+
+    /*! Append new content if the file already exists */
+    SftpAppendToExisting,
+
+    /*! If the file or directory already exists skip it */
+    SftpSkipExisting
+};
+
+/*!
+ * \brief The type of a remote file.
+ */
+enum SftpFileType { FileTypeRegular, FileTypeDirectory, FileTypeOther, FileTypeUnknown };
+
+/*!
+ * \brief Possible errors.
+*/
+enum SftpError { NoError, EndOfFile, FileNotFound, PermissionDenied, GenericFailure, BadMessage, NoConnection, ConnectionLost, UnsupportedOperation  };
+
+/*!
+    \brief Contains information about a remote file.
+*/
+class QSSH_EXPORT SftpFileInfo
+{
+public:
+    SftpFileInfo() : type(FileTypeUnknown), sizeValid(false), permissionsValid(false) { }
+
+    /// The remote file name, only file attribute required by the RFC to be present so this is always set
+    QString name;
+
+    /// The type of file
+    SftpFileType type = FileTypeUnknown;
+
+    /// The remote file size in bytes.
+    quint64 size = 0;
+
+    /// The permissions set on the file, might be empty as the RFC allows an SFTP server not to support any file attributes beyond the name.
+    QFileDevice::Permissions permissions{};
+
+    /// Last time file was accessed.
+    quint32 atime = 0;
+
+    /// Last time file was modified.
+    quint32 mtime = 0;
+
+    /// If the timestamps (\ref atime and \ref mtime) are valid, the RFC allows an SFTP server not to support any file attributes beyond the name.
+    bool timestampsValid = false;
+
+    /// The RFC allows an SFTP server not to support any file attributes beyond the name.
+    bool sizeValid = false;
+
+    /// The RFC allows an SFTP server not to support any file attributes beyond the name.
+    bool permissionsValid = false;
+};
+
+} // namespace QSsh
+
+#endif // SFTPDEFS_H

+ 407 - 0
src/tool/serverctrl/qssh/sftpfilesystemmodel.cpp

@@ -0,0 +1,407 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+#include "sftpfilesystemmodel.h"
+
+#include "sftpchannel.h"
+#include "sshconnection.h"
+#include "sshconnectionmanager.h"
+
+#include <QFileInfo>
+#include <QHash>
+#include <QIcon>
+#include <QList>
+#include <QString>
+
+namespace QSsh {
+namespace Internal {
+namespace {
+
+class SftpDirNode;
+class SftpFileNode
+{
+    Q_DISABLE_COPY(SftpFileNode)
+
+public:
+    SftpFileNode() : parent(nullptr) { }
+    virtual ~SftpFileNode() { }
+
+    QString path;
+    SftpFileInfo fileInfo;
+    SftpDirNode *parent;
+};
+
+class SftpDirNode : public SftpFileNode
+{
+    Q_DISABLE_COPY(SftpDirNode)
+
+public:
+    SftpDirNode() : lsState(LsNotYetCalled) { }
+    ~SftpDirNode() { qDeleteAll(children); }
+
+    enum { LsNotYetCalled, LsRunning, LsFinished } lsState;
+    QList<SftpFileNode *> children;
+};
+
+typedef QHash<SftpJobId, SftpDirNode *> DirNodeHash;
+
+SftpFileNode *indexToFileNode(const QModelIndex &index)
+{
+    return static_cast<SftpFileNode *>(index.internalPointer());
+}
+
+SftpDirNode *indexToDirNode(const QModelIndex &index)
+{
+    SftpFileNode * const fileNode = indexToFileNode(index);
+    QSSH_ASSERT(fileNode);
+    return dynamic_cast<SftpDirNode *>(fileNode);
+}
+
+} // anonymous namespace
+
+class SftpFileSystemModelPrivate
+{
+public:
+    SshConnection *sshConnection;
+    SftpChannel::Ptr sftpChannel;
+    QString rootDirectory;
+    SftpFileNode *rootNode;
+    SftpJobId statJobId;
+    DirNodeHash lsOps;
+    QList<SftpJobId> externalJobs;
+};
+} // namespace Internal
+
+using namespace Internal;
+
+SftpFileSystemModel::SftpFileSystemModel(QObject *parent)
+    : QAbstractItemModel(parent), d(new SftpFileSystemModelPrivate)
+{
+    d->sshConnection = nullptr;
+    d->rootDirectory = QLatin1Char('/');
+    d->rootNode = nullptr;
+    d->statJobId = SftpInvalidJob;
+}
+
+SftpFileSystemModel::~SftpFileSystemModel()
+{
+    shutDown();
+    delete d;
+}
+
+void SftpFileSystemModel::setSshConnection(const SshConnectionParameters &sshParams)
+{
+    QSSH_ASSERT_AND_RETURN(!d->sshConnection);
+    d->sshConnection = QSsh::acquireConnection(sshParams);
+    connect(d->sshConnection, &SshConnection::error,
+            this, &SftpFileSystemModel::handleSshConnectionFailure);
+    if (d->sshConnection->state() == SshConnection::Connected) {
+        handleSshConnectionEstablished();
+        return;
+    }
+    connect(d->sshConnection, &SshConnection::connected,
+            this, &SftpFileSystemModel::handleSshConnectionEstablished);
+    if (d->sshConnection->state() == SshConnection::Unconnected)
+        d->sshConnection->connectToHost();
+}
+
+void SftpFileSystemModel::setRootDirectory(const QString &path)
+{
+    beginResetModel();
+    d->rootDirectory = path;
+    delete d->rootNode;
+    d->rootNode = nullptr;
+    d->lsOps.clear();
+    d->statJobId = SftpInvalidJob;
+    endResetModel();
+    statRootDirectory();
+}
+
+QString SftpFileSystemModel::rootDirectory() const
+{
+    return d->rootDirectory;
+}
+
+SftpJobId SftpFileSystemModel::downloadFile(const QModelIndex &index, const QString &targetFilePath)
+{
+    QSSH_ASSERT_AND_RETURN_VALUE(d->rootNode, SftpInvalidJob);
+    const SftpFileNode * const fileNode = indexToFileNode(index);
+    QSSH_ASSERT_AND_RETURN_VALUE(fileNode, SftpInvalidJob);
+    QSSH_ASSERT_AND_RETURN_VALUE(fileNode->fileInfo.type == FileTypeRegular, SftpInvalidJob);
+    const SftpJobId jobId = d->sftpChannel->downloadFile(fileNode->path, targetFilePath,
+        SftpOverwriteExisting);
+    if (jobId != SftpInvalidJob)
+        d->externalJobs << jobId;
+    return jobId;
+}
+
+int SftpFileSystemModel::columnCount(const QModelIndex &parent) const
+{
+    Q_UNUSED(parent);
+    return 2; // type + name
+}
+
+QVariant SftpFileSystemModel::data(const QModelIndex &index, int role) const
+{
+    if (!index.internalPointer()) {
+        return QVariant();
+    }
+
+    const SftpFileNode * const node = indexToFileNode(index);
+    if (index.column() == 0 && role == Qt::DecorationRole) {
+        switch (node->fileInfo.type) {
+        case FileTypeRegular:
+        case FileTypeOther:
+            return QIcon(QStringLiteral(":/ssh/images/unknownfile.png"));
+        case FileTypeDirectory:
+            return QIcon(QStringLiteral(":/ssh/images/dir.png"));
+        case FileTypeUnknown:
+            return QIcon(QStringLiteral(":/ssh/images/help.png")); // Shows a question mark.
+        }
+    }
+    if (index.column() == 1) {
+        if (role == Qt::DisplayRole)
+            return node->fileInfo.name;
+        if (role == PathRole)
+            return node->path;
+    }
+    return QVariant();
+}
+
+Qt::ItemFlags SftpFileSystemModel::flags(const QModelIndex &index) const
+{
+    if (!index.isValid())
+        return Qt::NoItemFlags;
+    return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+}
+
+QVariant SftpFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    if (orientation != Qt::Horizontal)
+        return QVariant();
+    if (role != Qt::DisplayRole)
+        return QVariant();
+    if (section == 0)
+        return tr("File Type");
+    if (section == 1)
+        return tr("File Name");
+    return QVariant();
+}
+
+QModelIndex SftpFileSystemModel::index(int row, int column, const QModelIndex &parent) const
+{
+    if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent))
+        return QModelIndex();
+    if (!d->rootNode)
+        return QModelIndex();
+    if (!parent.isValid())
+        return createIndex(row, column, d->rootNode);
+    const SftpDirNode * const parentNode = indexToDirNode(parent);
+    QSSH_ASSERT_AND_RETURN_VALUE(parentNode, QModelIndex());
+    QSSH_ASSERT_AND_RETURN_VALUE(row < parentNode->children.count(), QModelIndex());
+    SftpFileNode * const childNode = parentNode->children.at(row);
+    return createIndex(row, column, childNode);
+}
+
+QModelIndex SftpFileSystemModel::parent(const QModelIndex &child) const
+{
+    if (!child.isValid()) // Don't assert on this, since the model tester tries it.
+        return QModelIndex();
+
+    const SftpFileNode * const childNode = indexToFileNode(child);
+    QSSH_ASSERT_AND_RETURN_VALUE(childNode, QModelIndex());
+    if (childNode == d->rootNode)
+        return QModelIndex();
+    SftpDirNode * const parentNode = childNode->parent;
+    if (parentNode == d->rootNode)
+        return createIndex(0, 0, d->rootNode);
+    const SftpDirNode * const grandParentNode = parentNode->parent;
+    QSSH_ASSERT_AND_RETURN_VALUE(grandParentNode, QModelIndex());
+    return createIndex(grandParentNode->children.indexOf(parentNode), 0, parentNode);
+}
+
+int SftpFileSystemModel::rowCount(const QModelIndex &parent) const
+{
+    if (!d->rootNode)
+        return 1; // fake it until we make it, otherwise QTreeView isn't happy
+    if (!parent.isValid())
+        return 1;
+    if (parent.column() != 0)
+        return 0;
+    SftpDirNode * const dirNode = indexToDirNode(parent);
+    if (!dirNode)
+        return 0;
+    if (dirNode->lsState != SftpDirNode::LsNotYetCalled)
+        return dirNode->children.count();
+    d->lsOps.insert(d->sftpChannel->listDirectory(dirNode->path), dirNode);
+    dirNode->lsState = SftpDirNode::LsRunning;
+    return 0;
+}
+
+void SftpFileSystemModel::statRootDirectory()
+{
+    if (!d->sftpChannel) {
+        return;
+    }
+
+    d->statJobId = d->sftpChannel->statFile(d->rootDirectory);
+}
+
+void SftpFileSystemModel::shutDown()
+{
+    if (d->sftpChannel) {
+        disconnect(d->sftpChannel.data(), nullptr, this, nullptr);
+        d->sftpChannel->closeChannel();
+        d->sftpChannel.clear();
+    }
+    if (d->sshConnection) {
+        disconnect(d->sshConnection, nullptr, this, nullptr);
+        QSsh::releaseConnection(d->sshConnection);
+        d->sshConnection = nullptr;
+    }
+    delete d->rootNode;
+    d->rootNode = nullptr;
+}
+
+void SftpFileSystemModel::handleSshConnectionFailure()
+{
+    emit connectionError(d->sshConnection->errorString());
+    beginResetModel();
+    shutDown();
+    endResetModel();
+}
+
+void SftpFileSystemModel::handleSftpChannelInitialized()
+{
+    connect(d->sftpChannel.data(),
+        &SftpChannel::fileInfoAvailable,
+        this, &SftpFileSystemModel::handleFileInfo);
+    connect(d->sftpChannel.data(), &SftpChannel::finished,
+        this, &SftpFileSystemModel::handleSftpJobFinished);
+    statRootDirectory();
+}
+
+void SftpFileSystemModel::handleSshConnectionEstablished()
+{
+    d->sftpChannel = d->sshConnection->createSftpChannel();
+    connect(d->sftpChannel.data(), &SftpChannel::initialized,
+            this, &SftpFileSystemModel::handleSftpChannelInitialized);
+    connect(d->sftpChannel.data(), &SftpChannel::channelError,
+            this, &SftpFileSystemModel::handleSftpChannelError);
+    d->sftpChannel->initialize();
+}
+
+void SftpFileSystemModel::handleSftpChannelError(const QString &reason)
+{
+    emit connectionError(reason);
+    beginResetModel();
+    shutDown();
+    endResetModel();
+}
+
+void SftpFileSystemModel::handleFileInfo(SftpJobId jobId, const QList<SftpFileInfo> &fileInfoList)
+{
+    if (jobId == d->statJobId) {
+        QSSH_ASSERT_AND_RETURN(!d->rootNode);
+        beginInsertRows(QModelIndex(), 0, 0);
+        d->rootNode = new SftpDirNode;
+        d->rootNode->path = d->rootDirectory;
+        d->rootNode->fileInfo = fileInfoList.first();
+        d->rootNode->fileInfo.name = d->rootDirectory == QLatin1String("/")
+            ? d->rootDirectory : QFileInfo(d->rootDirectory).fileName();
+        endInsertRows();
+        return;
+    }
+    SftpDirNode * const parentNode = d->lsOps.value(jobId);
+    QSSH_ASSERT_AND_RETURN(parentNode);
+    QList<SftpFileInfo> filteredList;
+    for (const SftpFileInfo &fi : fileInfoList) {
+        if (fi.name != QLatin1String(".") && fi.name != QLatin1String("..")) {
+            filteredList << fi;
+        }
+    }
+    if (filteredList.isEmpty())
+        return;
+
+    if (parentNode->parent) {
+        QModelIndex parentIndex = createIndex(parentNode->parent->children.indexOf(parentNode), 0, parentNode);
+        beginInsertRows(parentIndex, rowCount(parentIndex), rowCount(parentIndex) + filteredList.count());
+    } else {
+        // root node
+        beginInsertRows(QModelIndex(), 0, 1);
+    }
+
+    for (const SftpFileInfo &fileInfo : filteredList) {
+        SftpFileNode *childNode;
+        if (fileInfo.type == FileTypeDirectory) {
+            childNode = new SftpDirNode;
+        } else {
+            childNode = new SftpFileNode;
+        }
+        childNode->path = parentNode->path;
+        if (!childNode->path.endsWith(QLatin1Char('/')))
+            childNode->path += QLatin1Char('/');
+        childNode->path += fileInfo.name;
+        childNode->fileInfo = fileInfo;
+        childNode->parent = parentNode;
+        parentNode->children << childNode;
+    }
+    endInsertRows();
+}
+
+void SftpFileSystemModel::handleSftpJobFinished(SftpJobId jobId, const SftpError error, const QString &errorMessage)
+{
+    Q_UNUSED(error);
+
+    if (jobId == d->statJobId) {
+        d->statJobId = SftpInvalidJob;
+        if (!errorMessage.isEmpty())
+            emit sftpOperationFailed(tr("Error getting \"stat\" info about \"%1\": %2")
+                .arg(rootDirectory(), errorMessage));
+        return;
+    }
+
+    DirNodeHash::Iterator it = d->lsOps.find(jobId);
+    if (it != d->lsOps.end()) {
+        QSSH_ASSERT(it.value()->lsState == SftpDirNode::LsRunning);
+        it.value()->lsState = SftpDirNode::LsFinished;
+        if (!errorMessage.isEmpty())
+            emit sftpOperationFailed(tr("Error listing contents of directory \"%1\": %2")
+                .arg(it.value()->path, errorMessage));
+        d->lsOps.erase(it);
+        return;
+    }
+
+    const int jobIndex = d->externalJobs.indexOf(jobId);
+    QSSH_ASSERT_AND_RETURN(jobIndex != -1);
+    d->externalJobs.removeAt(jobIndex);
+    emit sftpOperationFinished(jobId, errorMessage);
+}
+
+} // namespace QSsh

+ 107 - 0
src/tool/serverctrl/qssh/sftpfilesystemmodel.h

@@ -0,0 +1,107 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+#ifndef SFTPFILESYSTEMMODEL_H
+#define SFTPFILESYSTEMMODEL_H
+
+#include "sftpdefs.h"
+
+#include "ssh_global.h"
+
+#include <QAbstractItemModel>
+
+namespace QSsh {
+class SshConnectionParameters;
+
+namespace Internal { class SftpFileSystemModelPrivate; }
+
+// Very simple read-only model. Symbolic links are not followed.
+class QSSH_EXPORT SftpFileSystemModel : public QAbstractItemModel
+{
+    Q_OBJECT
+public:
+    explicit SftpFileSystemModel(QObject *parent = nullptr);
+    ~SftpFileSystemModel();
+
+    /*
+     * Once this is called, an SFTP connection is established and the model is populated.
+     * The effect of additional calls is undefined.
+     */
+    void setSshConnection(const SshConnectionParameters &sshParams);
+
+    void setRootDirectory(const QString &path); // Default is "/".
+    QString rootDirectory() const;
+
+    SftpJobId downloadFile(const QModelIndex &index, const QString &targetFilePath);
+
+    // Use this to get the full path of a file or directory.
+    static const int PathRole = Qt::UserRole;
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+signals:
+     /*
+      * E.g. "Permission denied". Note that this can happen without direct user intervention,
+      * due to e.g. the view calling rowCount() on a non-readable directory. This signal should
+      * therefore not result in a message box or similar, since it might occur very often.
+      */
+    void sftpOperationFailed(const QString &errorMessage);
+
+    /*
+     * This error is not recoverable. The model will not have any content after
+     * the signal has been emitted.
+     */
+    void connectionError(const QString &errorMessage);
+
+    // Success <=> error.isEmpty().
+    void sftpOperationFinished(QSsh::SftpJobId, const QString &error);
+
+private:
+    void handleSshConnectionEstablished();
+    void handleSshConnectionFailure();
+    void handleSftpChannelInitialized();
+    void handleSftpChannelError(const QString &reason);
+    void handleFileInfo(QSsh::SftpJobId jobId, const QList<QSsh::SftpFileInfo> &fileInfoList);
+    void handleSftpJobFinished(QSsh::SftpJobId jobId, const SftpError error, const QString &errorMessage);
+
+    int columnCount(const QModelIndex &parent = QModelIndex()) const;
+    Qt::ItemFlags flags(const QModelIndex &index) const;
+    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
+    QModelIndex parent(const QModelIndex &child) const;
+    int rowCount(const QModelIndex &parent = QModelIndex()) const;
+
+    void statRootDirectory();
+    void shutDown();
+
+    Internal::SftpFileSystemModelPrivate * const d;
+};
+
+} // namespace QSsh;
+
+#endif // SFTPFILESYSTEMMODEL_H

+ 223 - 0
src/tool/serverctrl/qssh/sftpincomingpacket.cpp

@@ -0,0 +1,223 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sftpincomingpacket_p.h"
+
+#include "sshexception_p.h"
+#include "sshlogging_p.h"
+#include "sshpacketparser_p.h"
+
+namespace QSsh {
+namespace Internal {
+
+SftpIncomingPacket::SftpIncomingPacket() : m_length(0)
+{
+}
+
+void SftpIncomingPacket::consumeData(QByteArray &newData)
+{
+    qCDebug(sshLog, "%s: current data size = %d, new data size = %d", Q_FUNC_INFO,
+        int(m_data.size()), int(newData.size()));
+
+    if (isComplete() || dataSize() + newData.size() < int(sizeof m_length))
+        return;
+
+    if (dataSize() < sizeof m_length) {
+        moveFirstBytes(m_data, newData, sizeof m_length - m_data.size());
+        m_length = SshPacketParser::asUint32(m_data, static_cast<quint32>(0));
+        if (m_length < static_cast<quint32>(TypeOffset + 1)
+            || m_length > MaxPacketSize) {
+            throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+                "Invalid length field in SFTP packet.");
+        }
+    }
+
+    moveFirstBytes(m_data, newData,
+        qMin<quint32>(m_length - dataSize() + 4, newData.size()));
+}
+
+void SftpIncomingPacket::moveFirstBytes(QByteArray &target, QByteArray &source,
+    int n)
+{
+    target.append(source.left(n));
+    source.remove(0, n);
+}
+
+bool SftpIncomingPacket::isComplete() const
+{
+    return m_length == dataSize() - 4;
+}
+
+void SftpIncomingPacket::clear()
+{
+    m_data.clear();
+    m_length = 0;
+}
+
+quint32 SftpIncomingPacket::extractServerVersion() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_FXP_VERSION);
+    try {
+        return SshPacketParser::asUint32(m_data, TypeOffset + 1);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_FXP_VERSION packet.");
+    }
+}
+
+SftpHandleResponse SftpIncomingPacket::asHandleResponse() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_FXP_HANDLE);
+    try {
+        SftpHandleResponse response;
+        quint32 offset = RequestIdOffset;
+        response.requestId = SshPacketParser::asUint32(m_data, &offset);
+        response.handle = SshPacketParser::asString(m_data, &offset);
+        return response;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_FXP_HANDLE packet");
+    }
+}
+
+SftpStatusResponse SftpIncomingPacket::asStatusResponse() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_FXP_STATUS);
+    try {
+        SftpStatusResponse response;
+        quint32 offset = RequestIdOffset;
+        response.requestId = SshPacketParser::asUint32(m_data, &offset);
+        response.status = static_cast<SftpStatusCode>(SshPacketParser::asUint32(m_data, &offset));
+        response.errorString = SshPacketParser::asUserString(m_data, &offset);
+        response.language = SshPacketParser::asString(m_data, &offset);
+        return response;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_FXP_STATUS packet.");
+    }
+}
+
+SftpNameResponse SftpIncomingPacket::asNameResponse() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_FXP_NAME);
+    try {
+        SftpNameResponse response;
+        quint32 offset = RequestIdOffset;
+        response.requestId = SshPacketParser::asUint32(m_data, &offset);
+        const quint32 count = SshPacketParser::asUint32(m_data, &offset);
+        for (quint32 i = 0; i < count; ++i)
+            response.files << asFile(offset);
+        return response;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_FXP_NAME packet.");
+    }
+}
+
+SftpDataResponse SftpIncomingPacket::asDataResponse() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_FXP_DATA);
+    try {
+        SftpDataResponse response;
+        quint32 offset = RequestIdOffset;
+        response.requestId = SshPacketParser::asUint32(m_data, &offset);
+        response.data = SshPacketParser::asString(m_data, &offset);
+        return response;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_FXP_DATA packet.");
+    }
+}
+
+SftpAttrsResponse SftpIncomingPacket::asAttrsResponse() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_FXP_ATTRS);
+    try {
+        SftpAttrsResponse response;
+        quint32 offset = RequestIdOffset;
+        response.requestId = SshPacketParser::asUint32(m_data, &offset);
+        response.attrs = asFileAttributes(offset);
+        return response;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_FXP_ATTRS packet.");
+    }
+}
+
+SftpFile SftpIncomingPacket::asFile(quint32 &offset) const
+{
+    SftpFile file;
+    file.fileName
+        = QString::fromUtf8(SshPacketParser::asString(m_data, &offset));
+    file.longName
+        = QString::fromUtf8(SshPacketParser::asString(m_data, &offset));
+    file.attributes = asFileAttributes(offset);
+    return file;
+}
+
+SftpFileAttributes SftpIncomingPacket::asFileAttributes(quint32 &offset) const
+{
+    SftpFileAttributes attributes;
+    const quint32 flags = SshPacketParser::asUint32(m_data, &offset);
+    attributes.sizePresent = flags & SSH_FILEXFER_ATTR_SIZE;
+    attributes.timesPresent = flags & SSH_FILEXFER_ATTR_ACMODTIME;
+    attributes.uidAndGidPresent = flags & SSH_FILEXFER_ATTR_UIDGID;
+    attributes.permissionsPresent = flags & SSH_FILEXFER_ATTR_PERMISSIONS;
+    if (attributes.sizePresent) {
+        attributes.size = SshPacketParser::asUint64(m_data, &offset);
+    }
+    if (attributes.uidAndGidPresent) {
+        attributes.uid = SshPacketParser::asUint32(m_data, &offset);
+        attributes.gid = SshPacketParser::asUint32(m_data, &offset);
+    }
+    if (attributes.permissionsPresent)
+        attributes.permissions = SshPacketParser::asUint32(m_data, &offset);
+    if (attributes.timesPresent) {
+        attributes.atime = SshPacketParser::asUint32(m_data, &offset);
+        attributes.mtime = SshPacketParser::asUint32(m_data, &offset);
+    }
+    if (flags & SSH_FILEXFER_ATTR_EXTENDED) {
+        const quint32 count = SshPacketParser::asUint32(m_data, &offset);
+        for (quint32 i = 0; i < count; ++i) {
+            SshPacketParser::asString(m_data, &offset);
+            SshPacketParser::asString(m_data, &offset);
+        }
+    }
+    return attributes;
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 112 - 0
src/tool/serverctrl/qssh/sftpincomingpacket_p.h

@@ -0,0 +1,112 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SFTPINCOMINGPACKET_P_H
+#define SFTPINCOMINGPACKET_P_H
+
+#include "sftppacket_p.h"
+
+namespace QSsh {
+namespace Internal {
+
+struct SftpHandleResponse {
+    quint32 requestId;
+    QByteArray handle;
+};
+
+struct SftpStatusResponse {
+    quint32 requestId;
+    SftpStatusCode status;
+    QString errorString;
+    QByteArray language;
+};
+
+struct SftpFileAttributes {
+    bool sizePresent;
+    bool timesPresent;
+    bool uidAndGidPresent;
+    bool permissionsPresent;
+    quint64 size;
+    quint32 uid;
+    quint32 gid;
+    quint32 permissions;
+    quint32 atime;
+    quint32 mtime;
+};
+
+struct SftpFile {
+    QString fileName;
+    QString longName; // Not present in later RFCs, so we don't expose this to the user.
+    SftpFileAttributes attributes;
+};
+
+struct SftpNameResponse {
+    quint32 requestId;
+    QList<SftpFile> files;
+};
+
+struct SftpDataResponse {
+    quint32 requestId;
+    QByteArray data;
+};
+
+struct SftpAttrsResponse {
+    quint32 requestId;
+    SftpFileAttributes attrs;
+};
+
+class SftpIncomingPacket : public AbstractSftpPacket
+{
+public:
+    SftpIncomingPacket();
+
+    void consumeData(QByteArray &data);
+    void clear();
+    bool isComplete() const;
+    quint32 extractServerVersion() const;
+    SftpHandleResponse asHandleResponse() const;
+    SftpStatusResponse asStatusResponse() const;
+    SftpNameResponse asNameResponse() const;
+    SftpDataResponse asDataResponse() const;
+    SftpAttrsResponse asAttrsResponse() const;
+
+private:
+    void moveFirstBytes(QByteArray &target, QByteArray &source, int n);
+
+    SftpFileAttributes asFileAttributes(quint32 &offset) const;
+    SftpFile asFile(quint32 &offset) const;
+
+    quint32 m_length;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SFTPINCOMINGPACKET_P_H

+ 236 - 0
src/tool/serverctrl/qssh/sftpoperation.cpp

@@ -0,0 +1,236 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sftpoperation_p.h"
+
+#include "sftpoutgoingpacket_p.h"
+
+#include <QFile>
+
+namespace QSsh {
+namespace Internal {
+
+AbstractSftpOperation::AbstractSftpOperation(SftpJobId jobId) : jobId(jobId)
+{
+}
+
+AbstractSftpOperation::~AbstractSftpOperation() { }
+
+
+SftpStatFile::SftpStatFile(SftpJobId jobId, const QString &path)
+    : AbstractSftpOperation(jobId), path(path)
+{
+}
+
+SftpOutgoingPacket &SftpStatFile::initialPacket(SftpOutgoingPacket &packet)
+{
+    return packet.generateStat(path, jobId);
+}
+
+SftpMakeDir::SftpMakeDir(SftpJobId jobId, const QString &path,
+    const SftpUploadDir::Ptr &parentJob)
+    : AbstractSftpOperation(jobId), parentJob(parentJob), remoteDir(path)
+{
+}
+
+SftpOutgoingPacket &SftpMakeDir::initialPacket(SftpOutgoingPacket &packet)
+{
+    return packet.generateMkDir(remoteDir, jobId);
+}
+
+
+SftpRmDir::SftpRmDir(SftpJobId id, const QString &path)
+    : AbstractSftpOperation(id), remoteDir(path)
+{
+}
+
+SftpOutgoingPacket &SftpRmDir::initialPacket(SftpOutgoingPacket &packet)
+{
+    return packet.generateRmDir(remoteDir, jobId);
+}
+
+
+SftpRm::SftpRm(SftpJobId jobId, const QString &path)
+    : AbstractSftpOperation(jobId), remoteFile(path) {}
+
+SftpOutgoingPacket &SftpRm::initialPacket(SftpOutgoingPacket &packet)
+{
+    return packet.generateRm(remoteFile, jobId);
+}
+
+
+SftpRename::SftpRename(SftpJobId jobId, const QString &oldPath,
+    const QString &newPath)
+    : AbstractSftpOperation(jobId), oldPath(oldPath), newPath(newPath)
+{
+}
+
+SftpOutgoingPacket &SftpRename::initialPacket(SftpOutgoingPacket &packet)
+{
+    return packet.generateRename(oldPath, newPath, jobId);
+}
+
+
+SftpCreateLink::SftpCreateLink(SftpJobId jobId, const QString &filePath, const QString &target)
+    : AbstractSftpOperation(jobId), filePath(filePath), target(target)
+{
+}
+
+SftpOutgoingPacket &SftpCreateLink::initialPacket(SftpOutgoingPacket &packet)
+{
+    return packet.generateCreateLink(filePath, target, jobId);
+}
+
+
+AbstractSftpOperationWithHandle::AbstractSftpOperationWithHandle(SftpJobId jobId,
+    const QString &remotePath)
+    : AbstractSftpOperation(jobId),
+      remotePath(remotePath), state(Inactive), hasError(false)
+{
+}
+
+AbstractSftpOperationWithHandle::~AbstractSftpOperationWithHandle() { }
+
+
+SftpListDir::SftpListDir(SftpJobId jobId, const QString &path,
+    const QSharedPointer<SftpDownloadDir> &parentJob)
+    : AbstractSftpOperationWithHandle(jobId, path), parentJob(parentJob)
+{
+}
+
+SftpOutgoingPacket &SftpListDir::initialPacket(SftpOutgoingPacket &packet)
+{
+    state = OpenRequested;
+    return packet.generateOpenDir(remotePath, jobId);
+}
+
+
+SftpCreateFile::SftpCreateFile(SftpJobId jobId, const QString &path,
+    SftpOverwriteMode mode)
+    : AbstractSftpOperationWithHandle(jobId, path), mode(mode)
+{
+}
+
+SftpOutgoingPacket & SftpCreateFile::initialPacket(SftpOutgoingPacket &packet)
+{
+    state = OpenRequested;
+    return packet.generateOpenFileForWriting(remotePath, mode,
+        SftpOutgoingPacket::DefaultPermissions, jobId);
+}
+
+
+const int AbstractSftpTransfer::MaxInFlightCount = 10; // Experimentally found to be enough.
+
+AbstractSftpTransfer::AbstractSftpTransfer(SftpJobId jobId, const QString &remotePath,
+    const QSharedPointer<QIODevice> &localFile)
+    : AbstractSftpOperationWithHandle(jobId, remotePath),
+      localFile(localFile), fileSize(0), offset(0), inFlightCount(0),
+      statRequested(false)
+{
+}
+
+AbstractSftpTransfer::~AbstractSftpTransfer() {}
+
+void AbstractSftpTransfer::calculateInFlightCount(quint32 chunkSize)
+{
+    if (fileSize == 0) {
+        inFlightCount = 1;
+    } else {
+        inFlightCount = fileSize / chunkSize;
+        if (fileSize % chunkSize)
+            ++inFlightCount;
+        if (inFlightCount > MaxInFlightCount)
+            inFlightCount = MaxInFlightCount;
+    }
+}
+
+
+SftpDownload::SftpDownload(SftpJobId jobId, const QString &remotePath,
+    const QSharedPointer<QIODevice> &localFile, SftpOverwriteMode mode,
+    const QSharedPointer<QSsh::Internal::SftpDownloadDir> &parentJob)
+    : AbstractSftpTransfer(jobId, remotePath, localFile), eofId(SftpInvalidJob), mode(mode),
+      parentJob(parentJob)
+{
+}
+
+SftpOutgoingPacket &SftpDownload::initialPacket(SftpOutgoingPacket &packet)
+{
+    state = OpenRequested;
+    return packet.generateOpenFileForReading(remotePath, jobId);
+}
+
+
+SftpUploadFile::SftpUploadFile(SftpJobId jobId, const QString &remotePath,
+    const QSharedPointer<QIODevice> &localFile, SftpOverwriteMode mode,
+    const SftpUploadDir::Ptr &parentJob)
+    : AbstractSftpTransfer(jobId, remotePath, localFile),
+      parentJob(parentJob), mode(mode)
+{
+    fileSize = localFile->size();
+}
+
+SftpOutgoingPacket &SftpUploadFile::initialPacket(SftpOutgoingPacket &packet)
+{
+    state = OpenRequested;
+    quint32 permissions = 0;
+    QFileDevice *fileDevice = qobject_cast<QFileDevice*>(localFile.data());
+    if (fileDevice) {
+        const QFile::Permissions &qtPermissions = fileDevice->permissions();
+        if (qtPermissions & QFile::ExeOther)
+            permissions |= 1 << 0;
+        if (qtPermissions & QFile::WriteOther)
+            permissions |= 1 << 1;
+        if (qtPermissions & QFile::ReadOther)
+            permissions |= 1 << 2;
+        if (qtPermissions & QFile::ExeGroup)
+            permissions |= 1<< 3;
+        if (qtPermissions & QFile::WriteGroup)
+            permissions |= 1<< 4;
+        if (qtPermissions & QFile::ReadGroup)
+            permissions |= 1<< 5;
+        if (qtPermissions & QFile::ExeOwner)
+            permissions |= 1<< 6;
+        if (qtPermissions & QFile::WriteOwner)
+            permissions |= 1<< 7;
+        if (qtPermissions & QFile::ReadOwner)
+            permissions |= 1<< 8;
+    } else {
+        // write owner
+        permissions |= 1<< 7;
+        // read owner
+        permissions |= 1<< 8;
+    }
+    return packet.generateOpenFileForWriting(remotePath, mode, permissions, jobId);
+}
+
+SftpUploadDir::~SftpUploadDir() {}
+
+} // namespace Internal
+} // namespace QSsh

+ 290 - 0
src/tool/serverctrl/qssh/sftpoperation_p.h

@@ -0,0 +1,290 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SFTPOPERATION_P_H
+#define SFTPOPERATION_P_H
+
+#include "sftpdefs.h"
+
+#include <QByteArray>
+#include <QList>
+#include <QMap>
+#include <QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QIODevice;
+QT_END_NAMESPACE
+
+namespace QSsh {
+namespace Internal {
+
+class SftpOutgoingPacket;
+
+struct AbstractSftpOperation
+{
+    typedef QSharedPointer<AbstractSftpOperation> Ptr;
+    enum Type {
+        StatFile, ListDir, MakeDir, RmDir, Rm, Rename, CreateLink, CreateFile, Download, UploadFile
+    };
+
+    AbstractSftpOperation(SftpJobId jobId);
+    virtual ~AbstractSftpOperation();
+    virtual Type type() const = 0;
+    virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet) = 0;
+
+    const SftpJobId jobId;
+
+private:
+    AbstractSftpOperation(const AbstractSftpOperation &);
+    AbstractSftpOperation &operator=(const AbstractSftpOperation &);
+};
+
+struct SftpUploadDir;
+struct SftpDownloadDir;
+
+struct SftpStatFile : public AbstractSftpOperation
+{
+    typedef QSharedPointer<SftpStatFile> Ptr;
+
+    SftpStatFile(SftpJobId jobId, const QString &path);
+    virtual Type type() const { return StatFile; }
+    virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+    const QString path;
+};
+
+struct SftpMakeDir : public AbstractSftpOperation
+{
+    typedef QSharedPointer<SftpMakeDir> Ptr;
+
+    SftpMakeDir(SftpJobId jobId, const QString &path,
+        const QSharedPointer<SftpUploadDir> &parentJob = QSharedPointer<SftpUploadDir>());
+    virtual Type type() const { return MakeDir; }
+    virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+    const QSharedPointer<SftpUploadDir> parentJob;
+    const QString remoteDir;
+};
+
+struct SftpRmDir : public AbstractSftpOperation
+{
+    typedef QSharedPointer<SftpRmDir> Ptr;
+
+    SftpRmDir(SftpJobId id, const QString &path);
+    virtual Type type() const { return RmDir; }
+    virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+    const QString remoteDir;
+};
+
+struct SftpRm : public AbstractSftpOperation
+{
+    typedef QSharedPointer<SftpRm> Ptr;
+
+    SftpRm(SftpJobId jobId, const QString &path);
+    virtual Type type() const { return Rm; }
+    virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+    const QString remoteFile;
+};
+
+struct SftpRename : public AbstractSftpOperation
+{
+    typedef QSharedPointer<SftpRename> Ptr;
+
+    SftpRename(SftpJobId jobId, const QString &oldPath, const QString &newPath);
+    virtual Type type() const { return Rename; }
+    virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+    const QString oldPath;
+    const QString newPath;
+};
+
+struct SftpCreateLink : public AbstractSftpOperation
+{
+    typedef QSharedPointer<SftpCreateLink> Ptr;
+
+    SftpCreateLink(SftpJobId jobId, const QString &filePath, const QString &target);
+    virtual Type type() const { return CreateLink; }
+    virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+    const QString filePath;
+    const QString target;
+};
+
+
+struct AbstractSftpOperationWithHandle : public AbstractSftpOperation
+{
+    typedef QSharedPointer<AbstractSftpOperationWithHandle> Ptr;
+    enum State { Inactive, OpenRequested, Open, CloseRequested };
+
+    AbstractSftpOperationWithHandle(SftpJobId jobId, const QString &remotePath);
+    ~AbstractSftpOperationWithHandle();
+
+    const QString remotePath;
+    QByteArray remoteHandle;
+    State state;
+    bool hasError;
+};
+
+
+struct SftpListDir : public AbstractSftpOperationWithHandle
+{
+    typedef QSharedPointer<SftpListDir> Ptr;
+
+    SftpListDir(SftpJobId jobId, const QString &path,
+        const QSharedPointer<SftpDownloadDir> &parentJob = QSharedPointer<SftpDownloadDir>());
+    virtual Type type() const { return ListDir; }
+    virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+    const QSharedPointer<SftpDownloadDir> parentJob;
+};
+
+
+struct SftpCreateFile : public AbstractSftpOperationWithHandle
+{
+    typedef QSharedPointer<SftpCreateFile> Ptr;
+
+    SftpCreateFile(SftpJobId jobId, const QString &path, SftpOverwriteMode mode);
+    virtual Type type() const { return CreateFile; }
+    virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+    const SftpOverwriteMode mode;
+};
+
+struct AbstractSftpTransfer : public AbstractSftpOperationWithHandle
+{
+    typedef QSharedPointer<AbstractSftpTransfer> Ptr;
+
+    AbstractSftpTransfer(SftpJobId jobId, const QString &remotePath,
+        const QSharedPointer<QIODevice> &localFile);
+    ~AbstractSftpTransfer();
+    void calculateInFlightCount(quint32 chunkSize);
+
+    static const int MaxInFlightCount;
+
+    const QSharedPointer<QIODevice> localFile;
+    quint64 fileSize;
+    quint64 offset;
+    int inFlightCount;
+    bool statRequested;
+};
+
+struct SftpDownload : public AbstractSftpTransfer
+{
+    typedef QSharedPointer<SftpDownload> Ptr;
+    SftpDownload(SftpJobId jobId, const QString &remotePath,
+        const QSharedPointer<QIODevice> &localFile, SftpOverwriteMode mode,
+        const QSharedPointer<SftpDownloadDir> &parentJob = QSharedPointer<SftpDownloadDir>());
+    virtual Type type() const { return Download; }
+    virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+    QMap<quint32, quint64> offsets;
+    SftpJobId eofId;
+    SftpOverwriteMode mode;
+    const QSharedPointer<QSsh::Internal::SftpDownloadDir> parentJob;
+};
+
+struct SftpUploadFile : public AbstractSftpTransfer
+{
+    typedef QSharedPointer<SftpUploadFile> Ptr;
+
+    SftpUploadFile(SftpJobId jobId, const QString &remotePath,
+        const QSharedPointer<QIODevice> &localFile, SftpOverwriteMode mode,
+        const QSharedPointer<SftpUploadDir> &parentJob = QSharedPointer<SftpUploadDir>());
+    virtual Type type() const { return UploadFile; }
+    virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+    const QSharedPointer<SftpUploadDir> parentJob;
+    SftpOverwriteMode mode;
+};
+
+// Composite operation.
+struct SftpUploadDir
+{
+    typedef QSharedPointer<SftpUploadDir> Ptr;
+
+    struct Dir {
+        Dir(const QString &l, const QString &r) : localDir(l), remoteDir(r) {}
+        QString localDir;
+        QString remoteDir;
+    };
+
+    SftpUploadDir(SftpJobId jobId) : jobId(jobId), hasError(false) {}
+    ~SftpUploadDir();
+
+    void setError()
+    {
+        hasError = true;
+        uploadsInProgress.clear();
+        mkdirsInProgress.clear();
+    }
+
+    const SftpJobId jobId;
+    bool hasError;
+    QList<SftpUploadFile::Ptr> uploadsInProgress;
+    QMap<SftpMakeDir::Ptr, Dir> mkdirsInProgress;
+};
+
+// Composite operation.
+struct SftpDownloadDir
+{
+    typedef QSharedPointer<SftpDownloadDir> Ptr;
+
+    struct Dir {
+        Dir() {}
+        Dir(const QString &l, const QString &r) : localDir(l), remoteDir(r) {}
+        QString localDir;
+        QString remoteDir;
+    };
+
+    SftpDownloadDir(SftpJobId jobId, SftpOverwriteMode mode)
+        : jobId(jobId), hasError(false), mode(mode) {}
+
+    ~SftpDownloadDir() {}
+
+    void setError()
+    {
+        hasError = true;
+        downloadsInProgress.clear();
+        lsdirsInProgress.clear();
+    }
+
+    const SftpJobId jobId;
+    bool hasError;
+    SftpOverwriteMode mode;
+    QList<SftpDownload::Ptr> downloadsInProgress;
+    QMap<SftpListDir::Ptr, Dir> lsdirsInProgress;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SFTPOPERATION_P_H

+ 226 - 0
src/tool/serverctrl/qssh/sftpoutgoingpacket.cpp

@@ -0,0 +1,226 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sftpoutgoingpacket_p.h"
+
+#include "sshlogging_p.h"
+#include "sshpacket_p.h"
+
+#include <QtEndian>
+
+#include <limits>
+
+namespace QSsh {
+namespace Internal {
+
+namespace {
+    const quint32 DefaultAttributes = 0;
+    const quint32 SSH_FXF_READ = 0x00000001;
+    const quint32 SSH_FXF_WRITE = 0x00000002;
+    const quint32 SSH_FXF_APPEND = 0x00000004;
+    const quint32 SSH_FXF_CREAT = 0x00000008;
+    const quint32 SSH_FXF_TRUNC = 0x00000010;
+    const quint32 SSH_FXF_EXCL = 0x00000020;
+}
+
+SftpOutgoingPacket::SftpOutgoingPacket()
+{
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateInit(quint32 version)
+{
+    return init(SSH_FXP_INIT, 0).appendInt(version).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateStat(const QString &path, quint32 requestId)
+{
+    return init(SSH_FXP_LSTAT, requestId).appendString(path).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateOpenDir(const QString &path,
+    quint32 requestId)
+{
+    return init(SSH_FXP_OPENDIR, requestId).appendString(path).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateReadDir(const QByteArray &handle,
+    quint32 requestId)
+{
+    return init(SSH_FXP_READDIR, requestId).appendString(handle).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateCloseHandle(const QByteArray &handle,
+    quint32 requestId)
+{
+    return init(SSH_FXP_CLOSE, requestId).appendString(handle).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateMkDir(const QString &path,
+    quint32 requestId)
+{
+    return init(SSH_FXP_MKDIR, requestId).appendString(path)
+        .appendInt(DefaultAttributes).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateRmDir(const QString &path,
+    quint32 requestId)
+{
+    return init(SSH_FXP_RMDIR, requestId).appendString(path).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateRm(const QString &path,
+    quint32 requestId)
+{
+    return init(SSH_FXP_REMOVE, requestId).appendString(path).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateRename(const QString &oldPath,
+    const QString &newPath, quint32 requestId)
+{
+    return init(SSH_FXP_RENAME, requestId).appendString(oldPath)
+        .appendString(newPath).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFileForWriting(const QString &path,
+    SftpOverwriteMode mode, quint32 permissions, quint32 requestId)
+{
+    QList<quint32> attributes;
+    if (permissions != DefaultPermissions)
+        attributes << SSH_FILEXFER_ATTR_PERMISSIONS << permissions;
+    else
+        attributes << DefaultAttributes;
+    return generateOpenFile(path, Write, mode, attributes, requestId);
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFileForReading(const QString &path,
+    quint32 requestId)
+{
+    // Note: Overwrite mode is irrelevant and will be ignored.
+    return generateOpenFile(path, Read, SftpSkipExisting, QList<quint32>() << DefaultAttributes,
+        requestId);
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateReadFile(const QByteArray &handle,
+    quint64 offset, quint32 length, quint32 requestId)
+{
+    return init(SSH_FXP_READ, requestId).appendString(handle).appendInt64(offset)
+        .appendInt(length).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateFstat(const QByteArray &handle,
+    quint32 requestId)
+{
+    return init(SSH_FXP_FSTAT, requestId).appendString(handle).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateWriteFile(const QByteArray &handle,
+    quint64 offset, const QByteArray &data, quint32 requestId)
+{
+    return init(SSH_FXP_WRITE, requestId).appendString(handle)
+        .appendInt64(offset).appendString(data).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateCreateLink(const QString &filePath,
+    const QString &target, quint32 requestId)
+{
+    return init(SSH_FXP_SYMLINK, requestId).appendString(filePath).appendString(target).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFile(const QString &path,
+    OpenType openType, SftpOverwriteMode mode, const QList<quint32> &attributes, quint32 requestId)
+{
+    quint32 pFlags = 0;
+    switch (openType) {
+    case Read:
+        pFlags = SSH_FXF_READ;
+        break;
+    case Write:
+        pFlags = SSH_FXF_WRITE | SSH_FXF_CREAT;
+        switch (mode) {
+        case SftpOverwriteExisting: pFlags |= SSH_FXF_TRUNC; break;
+        case SftpAppendToExisting: pFlags |= SSH_FXF_APPEND; break;
+        case SftpSkipExisting: pFlags |= SSH_FXF_EXCL; break;
+        }
+        break;
+    }
+
+    init(SSH_FXP_OPEN, requestId).appendString(path).appendInt(pFlags);
+    for (const quint32 attribute : attributes) {
+        appendInt(attribute);
+    }
+    return finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::init(SftpPacketType type,
+    quint32 requestId)
+{
+    m_data.resize(TypeOffset + 1);
+    m_data[TypeOffset] = type;
+    if (type != SSH_FXP_INIT) {
+        appendInt(requestId);
+        qCDebug(sshLog, "Generating SFTP packet of type %d with request id %u", type, requestId);
+    }
+    return *this;
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::appendInt(quint32 val)
+{
+    m_data.append(AbstractSshPacket::encodeInt(val));
+    return *this;
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::appendInt64(quint64 value)
+{
+    m_data.append(AbstractSshPacket::encodeInt(value));
+    return *this;
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::appendString(const QString &string)
+{
+    m_data.append(AbstractSshPacket::encodeString(string.toUtf8()));
+    return *this;
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::appendString(const QByteArray &string)
+{
+    m_data += AbstractSshPacket::encodeString(string);
+    return *this;
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::finalize()
+{
+    AbstractSshPacket::setLengthField(m_data);
+    return *this;
+}
+
+const quint32 SftpOutgoingPacket::DefaultPermissions = std::numeric_limits<quint32>::max();
+
+} // namespace Internal
+} // namespace QSsh

+ 92 - 0
src/tool/serverctrl/qssh/sftpoutgoingpacket_p.h

@@ -0,0 +1,92 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SFTPOUTGOINGPACKET_P_H
+#define SFTPOUTGOINGPACKET_P_H
+
+#include "sftppacket_p.h"
+#include "sftpdefs.h"
+
+namespace QSsh {
+namespace Internal {
+
+class SftpOutgoingPacket : public AbstractSftpPacket
+{
+public:
+    SftpOutgoingPacket();
+    SftpOutgoingPacket &generateInit(quint32 version);
+    SftpOutgoingPacket &generateStat(const QString &path, quint32 requestId);
+    SftpOutgoingPacket &generateOpenDir(const QString &path, quint32 requestId);
+    SftpOutgoingPacket &generateReadDir(const QByteArray &handle,
+        quint32 requestId);
+    SftpOutgoingPacket &generateCloseHandle(const QByteArray &handle,
+        quint32 requestId);
+    SftpOutgoingPacket &generateMkDir(const QString &path, quint32 requestId);
+    SftpOutgoingPacket &generateRmDir(const QString &path, quint32 requestId);
+    SftpOutgoingPacket &generateRm(const QString &path, quint32 requestId);
+    SftpOutgoingPacket &generateRename(const QString &oldPath,
+        const QString &newPath, quint32 requestId);
+    SftpOutgoingPacket &generateOpenFileForWriting(const QString &path,
+         SftpOverwriteMode mode, quint32 permissions, quint32 requestId);
+    SftpOutgoingPacket &generateOpenFileForReading(const QString &path,
+        quint32 requestId);
+    SftpOutgoingPacket &generateReadFile(const QByteArray &handle,
+        quint64 offset, quint32 length, quint32 requestId);
+    SftpOutgoingPacket &generateFstat(const QByteArray &handle,
+        quint32 requestId);
+    SftpOutgoingPacket &generateWriteFile(const QByteArray &handle,
+        quint64 offset, const QByteArray &data, quint32 requestId);
+
+    // Note: OpenSSH's SFTP server has a bug that reverses the filePath and target
+    //       arguments, so this operation is not portable.
+    SftpOutgoingPacket &generateCreateLink(const QString &filePath, const QString &target,
+        quint32 requestId);
+
+    static const quint32 DefaultPermissions;
+
+private:
+    static QByteArray encodeString(const QString &string);
+
+    enum OpenType { Read, Write };
+    SftpOutgoingPacket &generateOpenFile(const QString &path, OpenType openType,
+        SftpOverwriteMode mode, const QList<quint32> &attributes, quint32 requestId);
+
+    SftpOutgoingPacket &init(SftpPacketType type, quint32 requestId);
+    SftpOutgoingPacket &appendInt(quint32 value);
+    SftpOutgoingPacket &appendInt64(quint64 value);
+    SftpOutgoingPacket &appendString(const QString &string);
+    SftpOutgoingPacket &appendString(const QByteArray &string);
+    SftpOutgoingPacket &finalize();
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SFTPOUTGOINGPACKET_P_H

+ 57 - 0
src/tool/serverctrl/qssh/sftppacket.cpp

@@ -0,0 +1,57 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sftppacket_p.h"
+
+#include "sshpacketparser_p.h"
+
+namespace QSsh {
+namespace Internal {
+
+// There's no "standard" or negotiation between server and client for this, so
+// just use the same as openssh's sftp implementation
+const quint32 AbstractSftpPacket::MaxDataSize = 32768;
+const quint32 AbstractSftpPacket::MaxPacketSize = 256 * 1024;
+
+const int AbstractSftpPacket::TypeOffset = 4;
+const int AbstractSftpPacket::RequestIdOffset = TypeOffset + 1;
+const int AbstractSftpPacket::PayloadOffset = RequestIdOffset + 4;
+
+AbstractSftpPacket::AbstractSftpPacket()
+{
+}
+
+quint32 AbstractSftpPacket::requestId() const
+{
+    return SshPacketParser::asUint32(m_data, RequestIdOffset);
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 117 - 0
src/tool/serverctrl/qssh/sftppacket_p.h

@@ -0,0 +1,117 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SFTPPACKET_P_H
+#define SFTPPACKET_P_H
+
+#include <QByteArray>
+#include <QList>
+#include <QString>
+
+namespace QSsh {
+namespace Internal {
+
+enum SftpPacketType {
+    SSH_FXP_INIT = 1,
+    SSH_FXP_VERSION = 2,
+    SSH_FXP_OPEN = 3,
+    SSH_FXP_CLOSE = 4,
+    SSH_FXP_READ = 5,
+    SSH_FXP_WRITE = 6,
+    SSH_FXP_LSTAT = 7,
+    SSH_FXP_FSTAT = 8,
+    SSH_FXP_SETSTAT = 9,
+    SSH_FXP_FSETSTAT = 10,
+    SSH_FXP_OPENDIR = 11,
+    SSH_FXP_READDIR = 12,
+    SSH_FXP_REMOVE = 13,
+    SSH_FXP_MKDIR = 14,
+    SSH_FXP_RMDIR = 15,
+    SSH_FXP_REALPATH = 16,
+    SSH_FXP_STAT = 17,
+    SSH_FXP_RENAME = 18,
+    SSH_FXP_READLINK = 19,
+    SSH_FXP_SYMLINK = 20, // Removed from later protocol versions. Try not to use.
+
+    SSH_FXP_STATUS = 101,
+    SSH_FXP_HANDLE = 102,
+    SSH_FXP_DATA = 103,
+    SSH_FXP_NAME = 104,
+    SSH_FXP_ATTRS = 105,
+
+    SSH_FXP_EXTENDED = 200,
+    SSH_FXP_EXTENDED_REPLY = 201
+};
+
+enum SftpStatusCode {
+    SSH_FX_OK = 0,
+    SSH_FX_EOF = 1,
+    SSH_FX_NO_SUCH_FILE = 2,
+    SSH_FX_PERMISSION_DENIED = 3,
+    SSH_FX_FAILURE = 4,
+    SSH_FX_BAD_MESSAGE = 5,
+    SSH_FX_NO_CONNECTION = 6,
+    SSH_FX_CONNECTION_LOST = 7,
+    SSH_FX_OP_UNSUPPORTED = 8
+};
+
+enum SftpAttributeType {
+    SSH_FILEXFER_ATTR_SIZE = 0x00000001,
+    SSH_FILEXFER_ATTR_UIDGID = 0x00000002,
+    SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004,
+    SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008,
+    SSH_FILEXFER_ATTR_EXTENDED = 0x80000000
+};
+
+class AbstractSftpPacket
+{
+public:
+    AbstractSftpPacket();
+    quint32 requestId() const;
+    const QByteArray &rawData() const { return m_data; }
+    SftpPacketType type() const { return static_cast<SftpPacketType>(m_data.at(TypeOffset)); }
+
+    static const quint32 MaxDataSize; // "Pure" data size per read/writepacket.
+    static const quint32 MaxPacketSize;
+
+protected:
+    quint32 dataSize() const { return static_cast<quint32>(m_data.size()); }
+
+    static const int TypeOffset;
+    static const int RequestIdOffset;
+    static const int PayloadOffset;
+
+    QByteArray m_data;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SFTPPACKET_P_H

+ 54 - 0
src/tool/serverctrl/qssh/ssh_global.h

@@ -0,0 +1,54 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSH_GLOBAL_H
+#define SSH_GLOBAL_H
+
+#include <QtGlobal>
+
+#ifdef _MSC_VER
+// For static cmake building removing dll export/import
+#  define QSSH_EXPORT
+#else
+
+#if defined(QTCSSH_LIBRARY)
+#  define QSSH_EXPORT Q_DECL_EXPORT
+#else
+#  define QSSH_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif
+
+#define QSSH_PRINT_WARNING qWarning("Soft assert at %s:%d", __FILE__, __LINE__)
+#define QSSH_ASSERT(cond) do { if (!(cond)) { QSSH_PRINT_WARNING; } } while (false)
+#define QSSH_ASSERT_AND_RETURN(cond) do { if (!(cond)) { QSSH_PRINT_WARNING; return; } } while (false)
+#define QSSH_ASSERT_AND_RETURN_VALUE(cond, value) do { if (!(cond)) { QSSH_PRINT_WARNING; return value; } } while (false)
+
+#endif // SSH_GLOBAL_H

+ 312 - 0
src/tool/serverctrl/qssh/sshagent.cpp

@@ -0,0 +1,312 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#include "sshagent_p.h"
+
+#include "sshlogging_p.h"
+#include "sshpacket_p.h"
+#include "sshpacketparser_p.h"
+#include "ssh_global.h"
+
+#include <QTimer>
+#include <QtEndian>
+
+#include <algorithm>
+
+namespace QSsh {
+namespace Internal {
+
+// https://github.com/openssh/openssh-portable/blob/V_7_2/PROTOCOL.agent
+enum PacketType {
+    SSH_AGENT_FAILURE = 5,
+    SSH2_AGENTC_REQUEST_IDENTITIES = 11,
+    SSH2_AGENTC_SIGN_REQUEST = 13,
+    SSH2_AGENT_IDENTITIES_ANSWER = 12,
+    SSH2_AGENT_SIGN_RESPONSE = 14,
+};
+
+// TODO: Remove once we require 5.7, where the endianness functions have a sane input type.
+template<typename T> static T fromBigEndian(const QByteArray &ba)
+{
+    return qFromBigEndian<T>(reinterpret_cast<const uchar *>(ba.constData()));
+}
+
+void SshAgent::refreshKeysImpl()
+{
+    if (state() != Connected)
+        return;
+    const auto keysRequestIt = std::find_if(m_pendingRequests.constBegin(),
+            m_pendingRequests.constEnd(), [](const Request &r) { return r.isKeysRequest(); });
+    if (keysRequestIt != m_pendingRequests.constEnd()) {
+        qCDebug(sshLog) << "keys request already pending, not adding another one";
+        return;
+    }
+    qCDebug(sshLog) << "queueing keys request";
+    m_pendingRequests << Request();
+    sendNextRequest();
+}
+
+void SshAgent::requestSignatureImpl(const QByteArray &key, uint token)
+{
+    if (state() != Connected)
+        return;
+    const QByteArray data = m_dataToSign.take(qMakePair(key, token));
+    QSSH_ASSERT(!data.isEmpty());
+    qCDebug(sshLog) << "queueing signature request";
+    m_pendingRequests.enqueue(Request(key, data, token));
+    sendNextRequest();
+}
+
+void SshAgent::sendNextRequest()
+{
+    if (m_pendingRequests.isEmpty())
+        return;
+    if (m_outgoingPacket.isComplete())
+        return;
+    if (hasError())
+        return;
+    const Request &request = m_pendingRequests.head();
+    m_outgoingPacket = request.isKeysRequest() ? generateKeysPacket() : generateSigPacket(request);
+    sendPacket();
+}
+
+SshAgent::Packet SshAgent::generateKeysPacket()
+{
+    qCDebug(sshLog) << "requesting keys from agent";
+    Packet p;
+    p.size = 1;
+    p.data += char(SSH2_AGENTC_REQUEST_IDENTITIES);
+    return p;
+}
+
+SshAgent::Packet SshAgent::generateSigPacket(const SshAgent::Request &request)
+{
+    qCDebug(sshLog) << "requesting signature from agent for key" << request.key << "and token"
+                    << request.token;
+    Packet p;
+    p.data += char(SSH2_AGENTC_SIGN_REQUEST);
+    p.data += AbstractSshPacket::encodeString(request.key);
+    p.data += AbstractSshPacket::encodeString(request.dataToSign);
+    p.data += AbstractSshPacket::encodeInt(quint32(0));
+    p.size = p.data.count();
+    return p;
+}
+
+SshAgent::~SshAgent()
+{
+    m_agentSocket.disconnect(this);
+}
+
+void SshAgent::storeDataToSign(const QByteArray &key, const QByteArray &data, uint token)
+{
+    instance().m_dataToSign.insert(qMakePair(key, token), data);
+}
+
+void SshAgent::removeDataToSign(const QByteArray &key, uint token)
+{
+    instance().m_dataToSign.remove(qMakePair(key, token));
+}
+
+SshAgent &QSsh::Internal::SshAgent::instance()
+{
+    static SshAgent agent;
+    return agent;
+}
+
+SshAgent::SshAgent()
+{
+    connect(&m_agentSocket, &QLocalSocket::connected, this, &SshAgent::handleConnected);
+    connect(&m_agentSocket, &QLocalSocket::disconnected, this, &SshAgent::handleDisconnected);
+//    connect(&m_agentSocket, &QLocalSocket::errorOccurred, this, &SshAgent::handleSocketError);
+    connect(&m_agentSocket, &QLocalSocket::readyRead, this, &SshAgent::handleIncomingData);
+    QTimer::singleShot(0, this, &SshAgent::connectToServer);
+}
+
+void SshAgent::connectToServer()
+{
+    const QByteArray serverAddress = qgetenv("SSH_AUTH_SOCK");
+    if (serverAddress.isEmpty()) {
+        qCDebug(sshLog) << "agent failure: socket address unknown";
+        m_error = tr("Cannot connect to ssh-agent: SSH_AUTH_SOCK is not set.");
+        emit errorOccurred();
+        return;
+    }
+    qCDebug(sshLog) << "connecting to ssh-agent socket" << serverAddress;
+    m_state = Connecting;
+    m_agentSocket.connectToServer(QString::fromLocal8Bit(serverAddress));
+}
+
+void SshAgent::handleConnected()
+{
+    m_state = Connected;
+    qCDebug(sshLog) << "connection to ssh-agent established";
+    refreshKeys();
+}
+
+void SshAgent::handleDisconnected()
+{
+    qCDebug(sshLog) << "lost connection to ssh-agent";
+    m_error = tr("Lost connection to ssh-agent for unknown reason.");
+    setDisconnected();
+}
+
+void SshAgent::handleSocketError()
+{
+    qCDebug(sshLog) << "agent socket error" << m_agentSocket.error();
+    m_error = m_agentSocket.errorString();
+    setDisconnected();
+}
+
+void SshAgent::handleIncomingData()
+{
+    qCDebug(sshLog) << "getting data from agent";
+    m_incomingData += m_agentSocket.readAll();
+    while (!hasError() && !m_incomingData.isEmpty()) {
+        if (m_incomingPacket.size == 0) {
+            if (m_incomingData.count() < int(sizeof m_incomingPacket.size))
+                break;
+            m_incomingPacket.size = fromBigEndian<quint32>(m_incomingData);
+            m_incomingData.remove(0, sizeof m_incomingPacket.size);
+        }
+        const int bytesToTake = qMin<quint32>(m_incomingPacket.size - m_incomingPacket.data.count(),
+                                              m_incomingData.count());
+        m_incomingPacket.data += m_incomingData.left(bytesToTake);
+        m_incomingData.remove(0, bytesToTake);
+        if (m_incomingPacket.isComplete())
+            handleIncomingPacket();
+        else
+            break;
+    }
+}
+
+void SshAgent::handleIncomingPacket()
+{
+    try {
+        qCDebug(sshLog) << "received packet from agent:" << m_incomingPacket.data.toHex();
+        const char messageType = m_incomingPacket.data.at(0);
+        switch (messageType) {
+        case SSH2_AGENT_IDENTITIES_ANSWER:
+            handleIdentitiesPacket();
+            break;
+        case SSH2_AGENT_SIGN_RESPONSE:
+            handleSignaturePacket();
+            break;
+        case SSH_AGENT_FAILURE:
+            if (m_pendingRequests.isEmpty()) {
+                qCWarning(sshLog) << "unexpected failure message from agent";
+            } else {
+                const Request request = m_pendingRequests.dequeue();
+                if (request.isSignatureRequest()) {
+                    qCWarning(sshLog) << "agent failed to sign message for key"
+                                      << request.key.toHex();
+                    emit signatureAvailable(request.key, QByteArray(), request.token);
+                } else {
+                    qCWarning(sshLog) << "agent failed to retrieve key list";
+                    if (m_keys.isEmpty()) {
+                        m_error = tr("ssh-agent failed to retrieve keys.");
+                        setDisconnected();
+                    }
+                }
+            }
+            break;
+        default:
+            qCWarning(sshLog) << "unexpected message type from agent:" << messageType;
+        }
+    } catch (const SshPacketParseException &) {
+        qCWarning(sshLog()) << "received malformed packet from agent";
+        handleProtocolError();
+    }
+    m_incomingPacket.invalidate();
+    m_incomingPacket.size = 0;
+    m_outgoingPacket.invalidate();
+    sendNextRequest();
+}
+
+void SshAgent::handleIdentitiesPacket()
+{
+    qCDebug(sshLog) << "got keys packet from agent";
+    if (m_pendingRequests.isEmpty() || !m_pendingRequests.dequeue().isKeysRequest()) {
+        qCDebug(sshLog) << "packet was not requested";
+        handleProtocolError();
+        return;
+    }
+    quint32 offset = 1;
+    const auto keyCount = SshPacketParser::asUint32(m_incomingPacket.data, &offset);
+    qCDebug(sshLog) << "packet contains" << keyCount << "keys";
+    QList<QByteArray> newKeys;
+    for (quint32 i = 0; i < keyCount; ++i) {
+        const QByteArray key = SshPacketParser::asString(m_incomingPacket.data, &offset);
+        quint32 keyOffset = 0;
+        const QByteArray algoName = SshPacketParser::asString(key, &keyOffset);
+        SshPacketParser::asString(key, &keyOffset); // rest of key blob
+        SshPacketParser::asString(m_incomingPacket.data, &offset); // comment
+        qCDebug(sshLog) << "adding key of type" << algoName;
+        newKeys << key;
+    }
+
+    m_keys = newKeys;
+    emit keysUpdated();
+}
+
+void SshAgent::handleSignaturePacket()
+{
+    qCDebug(sshLog) << "got signature packet from agent";
+    if (m_pendingRequests.isEmpty()) {
+        qCDebug(sshLog) << "signature packet was not requested";
+        handleProtocolError();
+        return;
+    }
+    const Request request = m_pendingRequests.dequeue();
+    if (!request.isSignatureRequest()) {
+        qCDebug(sshLog) << "signature packet was not requested";
+        handleProtocolError();
+        return;
+    }
+    const QByteArray signature = SshPacketParser::asString(m_incomingPacket.data, 1);
+    qCDebug(sshLog) << "signature for key" << request.key.toHex() << "is" << signature.toHex();
+    emit signatureAvailable(request.key, signature, request.token);
+}
+
+void SshAgent::handleProtocolError()
+{
+    m_error = tr("Protocol error when talking to ssh-agent.");
+    setDisconnected();
+}
+
+void SshAgent::setDisconnected()
+{
+    m_state = Unconnected;
+    m_agentSocket.disconnect(this);
+    emit errorOccurred();
+}
+
+void SshAgent::sendPacket()
+{
+    const quint32 sizeMsb = qToBigEndian(m_outgoingPacket.size);
+    m_agentSocket.write(reinterpret_cast<const char *>(&sizeMsb), sizeof sizeMsb);
+    m_agentSocket.write(m_outgoingPacket.data);
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 125 - 0
src/tool/serverctrl/qssh/sshagent_p.h

@@ -0,0 +1,125 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QByteArray>
+#include <QHash>
+#include <QList>
+#include <QLocalSocket>
+#include <QObject>
+#include <QPair>
+#include <QQueue>
+#include <QString>
+
+namespace QSsh {
+namespace Internal {
+
+class SshAgent : public QObject
+{
+    Q_OBJECT
+public:
+    enum State { Unconnected, Connecting, Connected, };
+
+    ~SshAgent();
+    static State state() { return instance().m_state; }
+    static bool hasError() { return !instance().m_error.isEmpty(); }
+    static QString errorString() { return instance().m_error; }
+    static QList<QByteArray> publicKeys() { return instance().m_keys; }
+
+    static void refreshKeys() { instance().refreshKeysImpl(); }
+    static void storeDataToSign(const QByteArray &key, const QByteArray &data, uint token);
+    static void removeDataToSign(const QByteArray &key, uint token);
+    static void requestSignature(const QByteArray &key, uint token) {
+        instance().requestSignatureImpl(key, token);
+    }
+
+    static SshAgent &instance();
+
+signals:
+    void errorOccurred();
+    void keysUpdated();
+
+    // Empty signature means signing failure.
+    void signatureAvailable(const QByteArray &key, const QByteArray &signature, uint token);
+
+private:
+    struct Request {
+        Request() { }
+        Request(const QByteArray &k, const QByteArray &d, uint t)
+            : key(k), dataToSign(d), token(t) { }
+
+        bool isKeysRequest() const { return !isSignatureRequest(); }
+        bool isSignatureRequest() const { return !key.isEmpty(); }
+
+        QByteArray key;
+        QByteArray dataToSign;
+        uint token = 0;
+    };
+
+    struct Packet {
+        bool isComplete() const { return size != 0 && int(size) == data.count(); }
+        void invalidate() { size = 0; data.clear(); }
+
+        quint32 size = 0;
+        QByteArray data;
+    };
+
+    SshAgent();
+    void connectToServer();
+    void refreshKeysImpl();
+    void requestSignatureImpl(const QByteArray &key, uint token);
+
+    void sendNextRequest();
+    Packet generateKeysPacket();
+    Packet generateSigPacket(const Request &request);
+
+    void handleConnected();
+    void handleDisconnected();
+    void handleSocketError();
+    void handleIncomingData();
+    void handleIncomingPacket();
+    void handleIdentitiesPacket();
+    void handleSignaturePacket();
+
+    void handleProtocolError();
+    void setDisconnected();
+
+    void sendPacket();
+
+    State m_state = Unconnected;
+    QString m_error;
+    QList<QByteArray> m_keys;
+    QHash<QPair<QByteArray, uint>, QByteArray> m_dataToSign;
+    QLocalSocket m_agentSocket;
+    QByteArray m_incomingData;
+    Packet m_incomingPacket;
+    Packet m_outgoingPacket;
+
+    QQueue<Request> m_pendingRequests;
+};
+
+} // namespace Internal
+} // namespace QSsh

+ 172 - 0
src/tool/serverctrl/qssh/sshbotanconversions_p.h

@@ -0,0 +1,172 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef BYTEARRAYCONVERSIONS_P_H
+#define BYTEARRAYCONVERSIONS_P_H
+
+#include "sshcapabilities_p.h"
+#include "sshexception_p.h"
+
+#include <botan/secmem.h>
+
+namespace QSsh {
+namespace Internal {
+
+inline const Botan::byte *convertByteArray(const QByteArray &a)
+{
+    return reinterpret_cast<const Botan::byte *>(a.constData());
+}
+
+inline Botan::byte *convertByteArray(QByteArray &a)
+{
+    return reinterpret_cast<Botan::byte *>(a.data());
+}
+
+inline QByteArray convertByteArray(const Botan::secure_vector<Botan::byte> &v)
+{
+    return QByteArray(reinterpret_cast<const char *>(v.data()), static_cast<int>(v.size()));
+}
+
+inline QByteArray convertByteArray(const std::vector<uint8_t> &v)
+{
+    return QByteArray(reinterpret_cast<const char *>(v.data()), v.size());
+}
+
+inline const char *botanKeyExchangeAlgoName(const QByteArray &rfcAlgoName)
+{
+    if (rfcAlgoName == SshCapabilities::DiffieHellmanGroup1Sha1)
+        return "modp/ietf/1024";
+    if (rfcAlgoName == SshCapabilities::DiffieHellmanGroup14Sha1)
+        return "modp/ietf/2048";
+    if (rfcAlgoName == SshCapabilities::EcdhNistp256)
+        return "secp256r1";
+    if (rfcAlgoName == SshCapabilities::EcdhNistp384)
+        return "secp384r1";
+    if (rfcAlgoName == SshCapabilities::EcdhNistp521)
+        return "secp521r1";
+    throw SshClientException(SshInternalError, SSH_TR("Unexpected key exchange algorithm \"%1\"")
+                             .arg(QString::fromLatin1(rfcAlgoName)));
+}
+
+inline const char *botanCipherAlgoName(const QByteArray &rfcAlgoName)
+{
+
+    if (rfcAlgoName == SshCapabilities::CryptAlgoAes128Cbc) {
+        return "CBC(AES-128)";
+    }
+    if (rfcAlgoName == SshCapabilities::CryptAlgoAes128Ctr) {
+        return "CTR(AES-128)";
+    }
+    if (rfcAlgoName == SshCapabilities::CryptAlgo3DesCbc) {
+        return "CBC(TripleDES)";
+    }
+    if (rfcAlgoName == SshCapabilities::CryptAlgo3DesCtr) {
+        return "CTR(TripleDES)";
+    }
+    if (rfcAlgoName == SshCapabilities::CryptAlgoAes192Ctr) {
+        return "CBR(AES-192)";
+    }
+    if (rfcAlgoName == SshCapabilities::CryptAlgoAes256Ctr) {
+        return "CTR(AES-256)";
+    }
+    throw SshClientException(SshInternalError, SSH_TR("Unexpected cipher \"%1\"")
+                             .arg(QString::fromLatin1(rfcAlgoName)));
+}
+
+
+inline const char *botanCryptAlgoName(const QByteArray &rfcAlgoName)
+{
+
+    if (rfcAlgoName == SshCapabilities::CryptAlgoAes128Cbc
+            || rfcAlgoName == SshCapabilities::CryptAlgoAes128Ctr) {
+        return "AES-128";
+    }
+    if (rfcAlgoName == SshCapabilities::CryptAlgo3DesCbc
+            || rfcAlgoName == SshCapabilities::CryptAlgo3DesCtr) {
+        return "TripleDES";
+    }
+    if (rfcAlgoName == SshCapabilities::CryptAlgoAes192Ctr) {
+        return "AES-192";
+    }
+    if (rfcAlgoName == SshCapabilities::CryptAlgoAes256Ctr) {
+        return "AES-256";
+    }
+    throw SshClientException(SshInternalError, SSH_TR("Unexpected cipher \"%1\"")
+                             .arg(QString::fromLatin1(rfcAlgoName)));
+}
+
+inline const char *botanEmsaAlgoName(const QByteArray &rfcAlgoName)
+{
+    if (rfcAlgoName == SshCapabilities::PubKeyDss)
+        return "EMSA1(SHA-1)";
+    if (rfcAlgoName == SshCapabilities::PubKeyRsa)
+        return "EMSA3(SHA-1)";
+    if (rfcAlgoName == SshCapabilities::PubKeyEcdsa256)
+        return "EMSA1(SHA-256)";
+    if (rfcAlgoName == SshCapabilities::PubKeyEcdsa384)
+        return "EMSA1_BSI(SHA-384)";
+    if (rfcAlgoName == SshCapabilities::PubKeyEcdsa521)
+        return "EMSA1_BSI(SHA-512)";
+    throw SshClientException(SshInternalError, SSH_TR("Unexpected host key algorithm \"%1\"")
+                             .arg(QString::fromLatin1(rfcAlgoName)));
+}
+
+inline const char *botanHMacAlgoName(const QByteArray &rfcAlgoName)
+{
+    if (rfcAlgoName == SshCapabilities::HMacSha1)
+        return "SHA-1";
+    if (rfcAlgoName == SshCapabilities::HMacSha256)
+        return "SHA-256";
+    if (rfcAlgoName == SshCapabilities::HMacSha384)
+        return "SHA-384";
+    if (rfcAlgoName == SshCapabilities::HMacSha512)
+        return "SHA-512";
+    throw SshClientException(SshInternalError, SSH_TR("Unexpected hashing algorithm \"%1\"")
+                             .arg(QString::fromLatin1(rfcAlgoName)));
+}
+
+inline quint32 botanHMacKeyLen(const QByteArray &rfcAlgoName)
+{
+    if (rfcAlgoName == SshCapabilities::HMacSha1)
+        return 20;
+    if (rfcAlgoName == SshCapabilities::HMacSha256)
+        return 32;
+    if (rfcAlgoName == SshCapabilities::HMacSha384)
+        return 48;
+    if (rfcAlgoName == SshCapabilities::HMacSha512)
+        return 64;
+    throw SshClientException(SshInternalError, SSH_TR("Unexpected hashing algorithm \"%1\"")
+                             .arg(QString::fromLatin1(rfcAlgoName)));
+}
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // BYTEARRAYCONVERSIONS_P_H

+ 180 - 0
src/tool/serverctrl/qssh/sshcapabilities.cpp

@@ -0,0 +1,180 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshcapabilities_p.h"
+
+#include "sshexception_p.h"
+
+#include <QCoreApplication>
+#include <QString>
+
+namespace QSsh {
+namespace Internal {
+
+namespace {
+    QByteArray listAsByteArray(const QList<QByteArray> &list)
+    {
+        QByteArray array;
+        for (const QByteArray &elem : list) {
+            array += elem + ',';
+        }
+        if (!array.isEmpty()) {
+            array.remove(array.count() - 1, 1);
+        }
+        return array;
+    }
+} // anonymous namspace
+
+const QByteArray SshCapabilities::DiffieHellmanGroup1Sha1("diffie-hellman-group1-sha1");
+const QByteArray SshCapabilities::DiffieHellmanGroup14Sha1("diffie-hellman-group14-sha1");
+const QByteArray SshCapabilities::EcdhKexNamePrefix("ecdh-sha2-nistp");
+const QByteArray SshCapabilities::EcdhNistp256 = EcdhKexNamePrefix + "256";
+const QByteArray SshCapabilities::EcdhNistp384 = EcdhKexNamePrefix + "384";
+const QByteArray SshCapabilities::EcdhNistp521 = EcdhKexNamePrefix + "521";
+const QList<QByteArray> SshCapabilities::KeyExchangeMethods = QList<QByteArray>()
+        << SshCapabilities::EcdhNistp256
+        << SshCapabilities::EcdhNistp384
+        << SshCapabilities::EcdhNistp521
+        << SshCapabilities::DiffieHellmanGroup1Sha1
+        << SshCapabilities::DiffieHellmanGroup14Sha1;
+
+const QByteArray SshCapabilities::PubKeyDss("ssh-dss");
+const QByteArray SshCapabilities::PubKeyRsa("ssh-rsa");
+const QByteArray SshCapabilities::PubKeyEcdsaPrefix("ecdsa-sha2-nistp");
+const QByteArray SshCapabilities::PubKeyEcdsa256 = SshCapabilities::PubKeyEcdsaPrefix + "256";
+const QByteArray SshCapabilities::PubKeyEcdsa384 = SshCapabilities::PubKeyEcdsaPrefix + "384";
+const QByteArray SshCapabilities::PubKeyEcdsa521 = SshCapabilities::PubKeyEcdsaPrefix + "521";
+const QList<QByteArray> SshCapabilities::PublicKeyAlgorithms = QList<QByteArray>()
+        << SshCapabilities::PubKeyEcdsa256
+        << SshCapabilities::PubKeyEcdsa384
+        << SshCapabilities::PubKeyEcdsa521
+        << SshCapabilities::PubKeyRsa
+        << SshCapabilities::PubKeyDss;
+
+const QByteArray SshCapabilities::CryptAlgo3DesCbc("3des-cbc");
+const QByteArray SshCapabilities::CryptAlgo3DesCtr("3des-ctr");
+const QByteArray SshCapabilities::CryptAlgoAes128Cbc("aes128-cbc");
+const QByteArray SshCapabilities::CryptAlgoAes128Ctr("aes128-ctr");
+const QByteArray SshCapabilities::CryptAlgoAes192Ctr("aes192-ctr");
+const QByteArray SshCapabilities::CryptAlgoAes256Ctr("aes256-ctr");
+const QList<QByteArray> SshCapabilities::EncryptionAlgorithms
+    = QList<QByteArray>() << SshCapabilities::CryptAlgoAes256Ctr
+                          << SshCapabilities::CryptAlgoAes192Ctr
+                          << SshCapabilities::CryptAlgoAes128Ctr
+                          << SshCapabilities::CryptAlgo3DesCtr
+                          << SshCapabilities::CryptAlgoAes128Cbc
+                          << SshCapabilities::CryptAlgo3DesCbc;
+
+const QByteArray SshCapabilities::HMacSha1("hmac-sha1");
+const QByteArray SshCapabilities::HMacSha196("hmac-sha1-96");
+const QByteArray SshCapabilities::HMacSha256("hmac-sha2-256");
+const QByteArray SshCapabilities::HMacSha384("hmac-sha2-384");
+const QByteArray SshCapabilities::HMacSha512("hmac-sha2-512");
+const QList<QByteArray> SshCapabilities::MacAlgorithms
+    = QList<QByteArray>() /* << SshCapabilities::HMacSha196 */
+        << SshCapabilities::HMacSha256
+        << SshCapabilities::HMacSha384
+        << SshCapabilities::HMacSha512
+        << SshCapabilities::HMacSha1;
+
+const QList<QByteArray> SshCapabilities::CompressionAlgorithms
+    = QList<QByteArray>() << "none";
+
+const QByteArray SshCapabilities::SshConnectionService("ssh-connection");
+
+QList<QByteArray> SshCapabilities::commonCapabilities(const QList<QByteArray> &myCapabilities,
+                                               const QList<QByteArray> &serverCapabilities, const QByteArray &group)
+{
+    QList<QByteArray> capabilities;
+    for (const QByteArray &myCapability : myCapabilities) {
+        if (serverCapabilities.contains(myCapability)) {
+            capabilities << myCapability;
+        }
+    }
+
+    if (!capabilities.isEmpty())
+        return capabilities;
+
+    throw SshServerException(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+        "Server and client capabilities do not match.",
+        QCoreApplication::translate("SshConnection",
+            "Server and client %1 capabilities don't match.\n"
+            "Client list: %2\n"
+            "Server list: %3")
+            .arg(QString::fromLatin1(group))
+            .arg(QString::fromLocal8Bit(listAsByteArray(myCapabilities)))
+            .arg(QString::fromLocal8Bit(listAsByteArray(serverCapabilities))));
+
+}
+
+QByteArray SshCapabilities::findBestMatch(const QList<QByteArray> &myCapabilities,
+    const QList<QByteArray> &serverCapabilities, const QByteArray &group)
+{
+    return commonCapabilities(myCapabilities, serverCapabilities, group).first();
+}
+
+int SshCapabilities::ecdsaIntegerWidthInBytes(const QByteArray &ecdsaAlgo)
+{
+    if (ecdsaAlgo == PubKeyEcdsa256)
+        return 32;
+    if (ecdsaAlgo == PubKeyEcdsa384)
+        return 48;
+    if (ecdsaAlgo == PubKeyEcdsa521)
+        return 66;
+    throw SshClientException(SshInternalError, SSH_TR("Unexpected ecdsa algorithm \"%1\"")
+                             .arg(QString::fromLatin1(ecdsaAlgo)));
+}
+
+QByteArray SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(int keyWidthInBytes)
+{
+    if (keyWidthInBytes <= 32)
+        return PubKeyEcdsa256;
+    if (keyWidthInBytes <= 48)
+        return PubKeyEcdsa384;
+    if (keyWidthInBytes <= 66)
+        return PubKeyEcdsa521;
+    throw SshClientException(SshInternalError, SSH_TR("Unexpected ecdsa key size (%1 bytes)")
+                             .arg(keyWidthInBytes));
+}
+
+const char *SshCapabilities::oid(const QByteArray &ecdsaAlgo)
+{
+    if (ecdsaAlgo == PubKeyEcdsa256)
+        return "secp256r1";
+    if (ecdsaAlgo == PubKeyEcdsa384)
+        return "secp384r1";
+    if (ecdsaAlgo == PubKeyEcdsa521)
+        return "secp521r1";
+    throw SshClientException(SshInternalError, SSH_TR("Unexpected ecdsa algorithm \"%1\"")
+                             .arg(QString::fromLatin1(ecdsaAlgo)));
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 91 - 0
src/tool/serverctrl/qssh/sshcapabilities_p.h

@@ -0,0 +1,91 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef CAPABILITIES_P_H
+#define CAPABILITIES_P_H
+
+#include <QByteArray>
+#include <QList>
+
+namespace QSsh {
+namespace Internal {
+
+class SshCapabilities
+{
+public:
+    static const QByteArray DiffieHellmanGroup1Sha1;
+    static const QByteArray DiffieHellmanGroup14Sha1;
+    static const QByteArray EcdhKexNamePrefix;
+    static const QByteArray EcdhNistp256;
+    static const QByteArray EcdhNistp384;
+    static const QByteArray EcdhNistp521; // sic
+    static const QList<QByteArray> KeyExchangeMethods;
+
+    static const QByteArray PubKeyDss;
+    static const QByteArray PubKeyRsa;
+    static const QByteArray PubKeyEcdsaPrefix;
+    static const QByteArray PubKeyEcdsa256;
+    static const QByteArray PubKeyEcdsa384;
+    static const QByteArray PubKeyEcdsa521;
+    static const QList<QByteArray> PublicKeyAlgorithms;
+
+    static const QByteArray CryptAlgo3DesCbc;
+    static const QByteArray CryptAlgo3DesCtr;
+    static const QByteArray CryptAlgoAes128Cbc;
+    static const QByteArray CryptAlgoAes128Ctr;
+    static const QByteArray CryptAlgoAes192Ctr;
+    static const QByteArray CryptAlgoAes256Ctr;
+    static const QList<QByteArray> EncryptionAlgorithms;
+
+    static const QByteArray HMacSha1;
+    static const QByteArray HMacSha196;
+    static const QByteArray HMacSha256;
+    static const QByteArray HMacSha384;
+    static const QByteArray HMacSha512;
+    static const QList<QByteArray> MacAlgorithms;
+
+    static const QList<QByteArray> CompressionAlgorithms;
+
+    static const QByteArray SshConnectionService;
+
+    static QList<QByteArray> commonCapabilities(const QList<QByteArray> &myCapabilities,
+                                                const QList<QByteArray> &serverCapabilities, const QByteArray &group);
+    static QByteArray findBestMatch(const QList<QByteArray> &myCapabilities,
+        const QList<QByteArray> &serverCapabilities, const QByteArray &group);
+
+    static int ecdsaIntegerWidthInBytes(const QByteArray &ecdsaAlgo);
+    static QByteArray ecdsaPubKeyAlgoForKeyWidth(int keyWidthInBytes);
+    static const char *oid(const QByteArray &ecdsaAlgo);
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // CAPABILITIES_P_H

+ 277 - 0
src/tool/serverctrl/qssh/sshchannel.cpp

@@ -0,0 +1,277 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshchannel_p.h"
+
+#include "sshincomingpacket_p.h"
+#include "sshsendfacility_p.h"
+#include "sshlogging_p.h"
+
+#include <botan/exceptn.h>
+
+#include <QTimer>
+
+namespace QSsh {
+namespace Internal {
+
+const quint32 NoChannel = 0xffffffffu;
+
+AbstractSshChannel::AbstractSshChannel(quint32 channelId,
+    SshSendFacility &sendFacility)
+    : m_sendFacility(sendFacility),
+      m_localChannel(channelId), m_remoteChannel(NoChannel),
+      m_localWindowSize(initialWindowSize()), m_remoteWindowSize(0),
+      m_state(Inactive)
+{
+    m_timeoutTimer.setTimerType(Qt::VeryCoarseTimer);
+    m_timeoutTimer.setSingleShot(true);
+    connect(&m_timeoutTimer, &QTimer::timeout, this, &AbstractSshChannel::timeout);
+}
+
+AbstractSshChannel::~AbstractSshChannel()
+{
+
+}
+
+void AbstractSshChannel::setChannelState(ChannelState state)
+{
+    m_state = state;
+    if (state == Closed)
+        closeHook();
+}
+
+void AbstractSshChannel::requestSessionStart()
+{
+    // Note: We are just being paranoid here about the Botan exceptions,
+    // which are extremely unlikely to happen, because if there was a problem
+    // with our cryptography stuff, it would have hit us before, on
+    // establishing the connection.
+    try {
+        m_sendFacility.sendSessionPacket(m_localChannel, initialWindowSize(), maxPacketSize());
+        setChannelState(SessionRequested);
+        m_timeoutTimer.start(ReplyTimeout);
+    }  catch (const std::exception &e) {
+        qCWarning(sshLog, "Botan error: %s", e.what());
+        closeChannel();
+    }
+}
+
+void AbstractSshChannel::sendData(const QByteArray &data)
+{
+    try {
+        m_sendBuffer += data;
+        flushSendBuffer();
+    }  catch (const std::exception &e) {
+        qCWarning(sshLog, "Botan error: %s", e.what());
+        closeChannel();
+    }
+}
+
+quint32 AbstractSshChannel::initialWindowSize()
+{
+    return maxPacketSize();
+}
+
+quint32 AbstractSshChannel::maxPacketSize()
+{
+    return 16 * 1024 * 1024;
+}
+
+void AbstractSshChannel::handleWindowAdjust(quint64 bytesToAdd)
+{
+    checkChannelActive();
+
+    const quint64 newValue = m_remoteWindowSize + bytesToAdd;
+    if (newValue > 0xffffffffu) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Illegal window size requested.");
+    }
+
+    m_remoteWindowSize = newValue;
+    flushSendBuffer();
+}
+
+void AbstractSshChannel::flushSendBuffer()
+{
+    while (true) {
+        const quint32 bytesToSend = qMin(m_remoteMaxPacketSize,
+                qMin<quint32>(m_remoteWindowSize, m_sendBuffer.size()));
+        if (bytesToSend == 0)
+            break;
+        const QByteArray &data = m_sendBuffer.left(bytesToSend);
+        m_sendFacility.sendChannelDataPacket(m_remoteChannel, data);
+        m_sendBuffer.remove(0, bytesToSend);
+        m_remoteWindowSize -= bytesToSend;
+    }
+}
+
+void AbstractSshChannel::handleOpenSuccess(quint32 remoteChannelId,
+    quint32 remoteWindowSize, quint32 remoteMaxPacketSize)
+{
+    const ChannelState oldState = m_state;
+    switch (oldState) {
+    case CloseRequested:   // closeChannel() was called while we were in SessionRequested state
+    case SessionRequested:
+        break; // Ok, continue.
+    default:
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet.");
+    }
+
+    m_timeoutTimer.stop();
+
+   qCDebug(sshLog, "Channel opened. remote channel id: %u, remote window size: %u, "
+       "remote max packet size: %u",
+       remoteChannelId, remoteWindowSize, remoteMaxPacketSize);
+   m_remoteChannel = remoteChannelId;
+   m_remoteWindowSize = remoteWindowSize;
+   m_remoteMaxPacketSize = remoteMaxPacketSize;
+   setChannelState(SessionEstablished);
+   if (oldState == CloseRequested)
+       closeChannel();
+   else
+       handleOpenSuccessInternal();
+}
+
+void AbstractSshChannel::handleOpenFailure(const QString &reason)
+{
+    switch (m_state) {
+    case SessionRequested:
+        break; // Ok, continue.
+    case CloseRequested:
+        return; // Late server reply; we requested a channel close in the meantime.
+    default:
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
+    }
+
+    m_timeoutTimer.stop();
+
+   qCDebug(sshLog, "Channel open request failed for channel %u", m_localChannel);
+   handleOpenFailureInternal(reason);
+}
+
+void AbstractSshChannel::handleChannelEof()
+{
+    if (m_state == Inactive || m_state == Closed) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_MSG_CHANNEL_EOF message.");
+    }
+    m_localWindowSize = 0;
+    emit eof();
+}
+
+void AbstractSshChannel::handleChannelClose()
+{
+    qCDebug(sshLog, "Receiving CLOSE for channel %u", m_localChannel);
+    if (channelState() == Inactive || channelState() == Closed) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_MSG_CHANNEL_CLOSE message.");
+    }
+    closeChannel();
+    setChannelState(Closed);
+}
+
+void AbstractSshChannel::handleChannelData(const QByteArray &data)
+{
+    const int bytesToDeliver = handleChannelOrExtendedChannelData(data);
+    handleChannelDataInternal(bytesToDeliver == data.size()
+        ? data : data.left(bytesToDeliver));
+}
+
+void AbstractSshChannel::handleChannelExtendedData(quint32 type, const QByteArray &data)
+{
+    const int bytesToDeliver = handleChannelOrExtendedChannelData(data);
+    handleChannelExtendedDataInternal(type, bytesToDeliver == data.size()
+        ? data : data.left(bytesToDeliver));
+}
+
+void AbstractSshChannel::handleChannelRequest(const SshIncomingPacket &packet)
+{
+    checkChannelActive();
+    const QByteArray &requestType = packet.extractChannelRequestType();
+    if (requestType == SshIncomingPacket::ExitStatusType)
+        handleExitStatus(packet.extractChannelExitStatus());
+    else if (requestType == SshIncomingPacket::ExitSignalType)
+        handleExitSignal(packet.extractChannelExitSignal());
+    else if (requestType != "eow@openssh.com") // Suppress warning for this one, as it's sent all the time.
+        qCWarning(sshLog, "Ignoring unknown request type '%s'", requestType.data());
+}
+
+int AbstractSshChannel::handleChannelOrExtendedChannelData(const QByteArray &data)
+{
+    checkChannelActive();
+
+    const int bytesToDeliver = qMin<quint32>(data.size(), maxDataSize());
+    if (bytesToDeliver != data.size())
+        qCWarning(sshLog, "Misbehaving server does not respect local window, clipping.");
+
+    m_localWindowSize -= bytesToDeliver;
+    if (m_localWindowSize < maxPacketSize()) {
+        m_localWindowSize += maxPacketSize();
+        m_sendFacility.sendWindowAdjustPacket(m_remoteChannel, maxPacketSize());
+    }
+    return bytesToDeliver;
+}
+
+void AbstractSshChannel::closeChannel()
+{
+    if (m_state == CloseRequested) {
+        m_timeoutTimer.stop();
+    } else if (m_state != Closed) {
+        if (m_state == Inactive) {
+            setChannelState(Closed);
+        } else {
+            const ChannelState oldState = m_state;
+            setChannelState(CloseRequested);
+            if (m_remoteChannel != NoChannel) {
+                m_sendFacility.sendChannelEofPacket(m_remoteChannel);
+                m_sendFacility.sendChannelClosePacket(m_remoteChannel);
+            } else {
+                QSSH_ASSERT(oldState == SessionRequested);
+            }
+        }
+    }
+}
+
+void AbstractSshChannel::checkChannelActive() const
+{
+    if (channelState() == Inactive || channelState() == Closed)
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Channel not open.");
+}
+
+quint32 AbstractSshChannel::maxDataSize() const
+{
+    return qMin(m_localWindowSize, maxPacketSize());
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 125 - 0
src/tool/serverctrl/qssh/sshchannel_p.h

@@ -0,0 +1,125 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHCHANNEL_P_H
+#define SSHCHANNEL_P_H
+
+#include <QByteArray>
+#include <QObject>
+#include <QString>
+#include <QTimer>
+
+namespace QSsh {
+namespace Internal {
+
+struct SshChannelExitSignal;
+struct SshChannelExitStatus;
+class SshIncomingPacket;
+class SshSendFacility;
+
+class AbstractSshChannel : public QObject
+{
+    Q_OBJECT
+public:
+    enum ChannelState {
+        Inactive, SessionRequested, SessionEstablished, CloseRequested, Closed
+    };
+
+    quint32 localChannelId() const { return m_localChannel; }
+    quint32 remoteChannel() const { return m_remoteChannel; }
+
+    virtual void handleChannelSuccess() = 0;
+    virtual void handleChannelFailure() = 0;
+
+    void handleOpenSuccess(quint32 remoteChannelId, quint32 remoteWindowSize,
+        quint32 remoteMaxPacketSize);
+    void handleOpenFailure(const QString &reason);
+    void handleWindowAdjust(quint64 bytesToAdd);
+    void handleChannelEof();
+    void handleChannelClose();
+    void handleChannelData(const QByteArray &data);
+    void handleChannelExtendedData(quint32 type, const QByteArray &data);
+    void handleChannelRequest(const SshIncomingPacket &packet);
+
+    void closeChannel();
+
+    virtual ~AbstractSshChannel();
+
+    static const int ReplyTimeout = 10000; // milli seconds
+    ChannelState channelState() const { return m_state; }
+
+signals:
+    void timeout();
+    void eof();
+
+protected:
+    AbstractSshChannel(quint32 channelId, SshSendFacility &sendFacility);
+
+    void setChannelState(ChannelState state);
+
+    void requestSessionStart();
+    void sendData(const QByteArray &data);
+
+    static quint32 initialWindowSize();
+    static quint32 maxPacketSize();
+
+    quint32 maxDataSize() const;
+    void checkChannelActive() const;
+
+    SshSendFacility &m_sendFacility;
+    QTimer m_timeoutTimer;
+
+private:
+    virtual void handleOpenSuccessInternal() = 0;
+    virtual void handleOpenFailureInternal(const QString &reason) = 0;
+    virtual void handleChannelDataInternal(const QByteArray &data) = 0;
+    virtual void handleChannelExtendedDataInternal(quint32 type,
+        const QByteArray &data) = 0;
+    virtual void handleExitStatus(const SshChannelExitStatus &exitStatus) = 0;
+    virtual void handleExitSignal(const SshChannelExitSignal &signal) = 0;
+
+    virtual void closeHook() = 0;
+
+    void flushSendBuffer();
+    int handleChannelOrExtendedChannelData(const QByteArray &data);
+
+    const quint32 m_localChannel;
+    quint32 m_remoteChannel;
+    quint32 m_localWindowSize;
+    quint32 m_remoteWindowSize;
+    quint32 m_remoteMaxPacketSize;
+    ChannelState m_state;
+    QByteArray m_sendBuffer;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHCHANNEL_P_H

+ 413 - 0
src/tool/serverctrl/qssh/sshchannelmanager.cpp

@@ -0,0 +1,413 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshchannelmanager_p.h"
+
+#include "sftpchannel.h"
+#include "sftpchannel_p.h"
+#include "sshdirecttcpiptunnel.h"
+#include "sshdirecttcpiptunnel_p.h"
+#include "sshforwardedtcpiptunnel.h"
+#include "sshforwardedtcpiptunnel_p.h"
+#include "sshincomingpacket_p.h"
+#include "sshlogging_p.h"
+#include "sshremoteprocess.h"
+#include "sshremoteprocess_p.h"
+#include "sshsendfacility_p.h"
+#include "sshtcpipforwardserver.h"
+#include "sshtcpipforwardserver_p.h"
+#include "sshx11channel_p.h"
+#include "sshx11inforetriever_p.h"
+
+#include <QList>
+
+namespace QSsh {
+namespace Internal {
+
+SshChannelManager::SshChannelManager(SshSendFacility &sendFacility,
+    QObject *parent)
+    : QObject(parent), m_sendFacility(sendFacility), m_nextLocalChannelId(0)
+{
+}
+
+void SshChannelManager::handleChannelRequest(const SshIncomingPacket &packet)
+{
+    lookupChannel(packet.extractRecipientChannel())
+        ->handleChannelRequest(packet);
+}
+
+void SshChannelManager::handleChannelOpen(const SshIncomingPacket &packet)
+{
+    const SshChannelOpenGeneric channelOpen = packet.extractChannelOpen();
+    if (channelOpen.channelType == SshIncomingPacket::ForwardedTcpIpType) {
+        handleChannelOpenForwardedTcpIp(channelOpen);
+        return;
+    }
+    if (channelOpen.channelType == "x11") {
+        handleChannelOpenX11(channelOpen);
+        return;
+    }
+    try {
+        m_sendFacility.sendChannelOpenFailurePacket(channelOpen.commonData.remoteChannel,
+                                                    SSH_OPEN_UNKNOWN_CHANNEL_TYPE, QByteArray());
+    }  catch (const std::exception &e) {
+        qCWarning(sshLog, "Botan error: %s", e.what());
+    }
+}
+
+void SshChannelManager::handleChannelOpenFailure(const SshIncomingPacket &packet)
+{
+   const SshChannelOpenFailure &failure = packet.extractChannelOpenFailure();
+   ChannelIterator it = lookupChannelAsIterator(failure.localChannel);
+   try {
+       it.value()->handleOpenFailure(failure.reasonString);
+   } catch (const SshServerException &e) {
+       Q_UNUSED(e);
+       removeChannel(it);
+       throw;
+   }
+   removeChannel(it);
+}
+
+void SshChannelManager::handleChannelOpenConfirmation(const SshIncomingPacket &packet)
+{
+   const SshChannelOpenConfirmation &confirmation
+       = packet.extractChannelOpenConfirmation();
+   lookupChannel(confirmation.localChannel)->handleOpenSuccess(confirmation.remoteChannel,
+       confirmation.remoteWindowSize, confirmation.remoteMaxPacketSize);
+}
+
+void SshChannelManager::handleChannelSuccess(const SshIncomingPacket &packet)
+{
+    lookupChannel(packet.extractRecipientChannel())->handleChannelSuccess();
+}
+
+void SshChannelManager::handleChannelFailure(const SshIncomingPacket &packet)
+{
+    lookupChannel(packet.extractRecipientChannel())->handleChannelFailure();
+}
+
+void SshChannelManager::handleChannelWindowAdjust(const SshIncomingPacket &packet)
+{
+    const SshChannelWindowAdjust adjust = packet.extractWindowAdjust();
+    lookupChannel(adjust.localChannel)->handleWindowAdjust(adjust.bytesToAdd);
+}
+
+void SshChannelManager::handleChannelData(const SshIncomingPacket &packet)
+{
+    const SshChannelData &data = packet.extractChannelData();
+    lookupChannel(data.localChannel)->handleChannelData(data.data);
+}
+
+void SshChannelManager::handleChannelExtendedData(const SshIncomingPacket &packet)
+{
+    const SshChannelExtendedData &data = packet.extractChannelExtendedData();
+    lookupChannel(data.localChannel)->handleChannelExtendedData(data.type, data.data);
+}
+
+void SshChannelManager::handleChannelEof(const SshIncomingPacket &packet)
+{
+    AbstractSshChannel * const channel
+        = lookupChannel(packet.extractRecipientChannel(), true);
+    if (channel)
+        channel->handleChannelEof();
+}
+
+void SshChannelManager::handleChannelClose(const SshIncomingPacket &packet)
+{
+    const quint32 channelId = packet.extractRecipientChannel();
+
+    ChannelIterator it = lookupChannelAsIterator(channelId, true);
+    if (it != m_channels.end()) {
+        it.value()->handleChannelClose();
+        removeChannel(it);
+    }
+}
+
+void SshChannelManager::handleRequestSuccess(const SshIncomingPacket &packet)
+{
+    if (m_waitingForwardServers.isEmpty()) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+                                 "Unexpected request success packet.",
+                                 tr("Unexpected request success packet."));
+    }
+    SshTcpIpForwardServer::Ptr server = m_waitingForwardServers.takeFirst();
+    if (server->state() == SshTcpIpForwardServer::Closing) {
+        server->setClosed();
+    } else if (server->state() == SshTcpIpForwardServer::Initializing) {
+        quint16 port = server->port();
+        if (port == 0)
+            port = packet.extractRequestSuccess().bindPort;
+        server->setListening(port);
+        m_listeningForwardServers.append(server);
+    } else {
+        QSSH_ASSERT(false);
+    }
+}
+
+void SshChannelManager::handleRequestFailure(const SshIncomingPacket &packet)
+{
+    Q_UNUSED(packet);
+    if (m_waitingForwardServers.isEmpty()) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+                                 "Unexpected request failure packet.",
+                                 tr("Unexpected request failure packet."));
+    }
+    SshTcpIpForwardServer::Ptr tunnel = m_waitingForwardServers.takeFirst();
+    tunnel->setClosed();
+}
+
+SshChannelManager::ChannelIterator SshChannelManager::lookupChannelAsIterator(quint32 channelId,
+    bool allowNotFound)
+{
+    ChannelIterator it = m_channels.find(channelId);
+    if (it == m_channels.end() && !allowNotFound) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid channel id.",
+            tr("Invalid channel id %1").arg(channelId));
+    }
+    return it;
+}
+
+AbstractSshChannel *SshChannelManager::lookupChannel(quint32 channelId,
+    bool allowNotFound)
+{
+    ChannelIterator it = lookupChannelAsIterator(channelId, allowNotFound);
+    return it == m_channels.end() ? nullptr : it.value();
+}
+
+QSsh::SshRemoteProcess::Ptr SshChannelManager::createRemoteProcess(const QByteArray &command)
+{
+    SshRemoteProcess::Ptr proc(new SshRemoteProcess(command, m_nextLocalChannelId++, m_sendFacility));
+    insertChannel(proc->d, proc);
+    connect(proc->d, &SshRemoteProcessPrivate::destroyed, this, [this] {
+        m_x11ForwardingRequests.removeOne(static_cast<SshRemoteProcessPrivate *>(sender()));
+    });
+    connect(proc->d, &SshRemoteProcessPrivate::x11ForwardingRequested, this,
+            [this, proc = proc->d](const QString &displayName) {
+        if (!x11DisplayName().isEmpty()) {
+            if (x11DisplayName() != displayName) {
+                proc->failToStart(tr("Cannot forward to display %1 on SSH connection that is "
+                                     "already forwarding to display %2.")
+                                  .arg(displayName, x11DisplayName()));
+                return;
+            }
+            if (!m_x11DisplayInfo.cookie.isEmpty())
+                proc->startProcess(m_x11DisplayInfo);
+            else
+                m_x11ForwardingRequests << proc;
+            return;
+        }
+        m_x11DisplayInfo.displayName = displayName;
+        m_x11ForwardingRequests << proc;
+        auto * const x11InfoRetriever = new SshX11InfoRetriever(displayName, this);
+        const auto failureHandler = [this](const QString &errorMessage) {
+            for (SshRemoteProcessPrivate * const proc : qAsConst(m_x11ForwardingRequests))
+                proc->failToStart(errorMessage);
+            m_x11ForwardingRequests.clear();
+        };
+        connect(x11InfoRetriever, &SshX11InfoRetriever::failure, this, failureHandler);
+        const auto successHandler = [this](const X11DisplayInfo &displayInfo) {
+            m_x11DisplayInfo = displayInfo;
+            for (SshRemoteProcessPrivate * const proc : qAsConst(m_x11ForwardingRequests))
+                proc->startProcess(displayInfo);
+            m_x11ForwardingRequests.clear();
+        };
+        connect(x11InfoRetriever, &SshX11InfoRetriever::success, this, successHandler);
+        qCDebug(sshLog) << "starting x11 info retriever";
+        x11InfoRetriever->start();
+    });
+    return proc;
+}
+
+QSsh::SshRemoteProcess::Ptr SshChannelManager::createRemoteShell()
+{
+    SshRemoteProcess::Ptr proc(new SshRemoteProcess(m_nextLocalChannelId++, m_sendFacility));
+    insertChannel(proc->d, proc);
+    return proc;
+}
+
+QSsh::SftpChannel::Ptr SshChannelManager::createSftpChannel()
+{
+    SftpChannel::Ptr sftp(new SftpChannel(m_nextLocalChannelId++, m_sendFacility));
+    insertChannel(sftp->d, sftp);
+    return sftp;
+}
+
+SshDirectTcpIpTunnel::Ptr SshChannelManager::createDirectTunnel(const QString &originatingHost,
+        quint16 originatingPort, const QString &remoteHost, quint16 remotePort)
+{
+    SshDirectTcpIpTunnel::Ptr tunnel(new SshDirectTcpIpTunnel(m_nextLocalChannelId++,
+            originatingHost, originatingPort, remoteHost, remotePort, m_sendFacility));
+    insertChannel(tunnel->d, tunnel);
+    return tunnel;
+}
+
+SshTcpIpForwardServer::Ptr SshChannelManager::createForwardServer(const QString &remoteHost,
+        quint16 remotePort)
+{
+    SshTcpIpForwardServer::Ptr server(new SshTcpIpForwardServer(remoteHost, remotePort,
+                                                                m_sendFacility));
+    connect(server.data(), &SshTcpIpForwardServer::stateChanged,
+            this, [this, server](SshTcpIpForwardServer::State state) {
+        switch (state) {
+        case SshTcpIpForwardServer::Closing:
+            m_listeningForwardServers.removeOne(server);
+            Q_FALLTHROUGH();
+        case SshTcpIpForwardServer::Initializing:
+            m_waitingForwardServers.append(server);
+            break;
+        case SshTcpIpForwardServer::Listening:
+        case SshTcpIpForwardServer::Inactive:
+            break;
+        }
+    });
+    return server;
+}
+
+void SshChannelManager::insertChannel(AbstractSshChannel *priv,
+    const QSharedPointer<QObject> &pub)
+{
+    connect(priv, &AbstractSshChannel::timeout, this, &SshChannelManager::timeout);
+    m_channels.insert(priv->localChannelId(), priv);
+    m_sessions.insert(priv, pub);
+}
+
+void SshChannelManager::handleChannelOpenForwardedTcpIp(
+        const SshChannelOpenGeneric &channelOpenGeneric)
+{
+    const SshChannelOpenForwardedTcpIp channelOpen
+            = SshIncomingPacket::extractChannelOpenForwardedTcpIp(channelOpenGeneric);
+
+    SshTcpIpForwardServer::Ptr server;
+
+    for (const SshTcpIpForwardServer::Ptr &candidate : m_listeningForwardServers) {
+        if (candidate->port() == channelOpen.remotePort
+                && candidate->bindAddress().toUtf8() == channelOpen.remoteAddress) {
+            server = candidate;
+            break;
+        }
+    };
+
+
+    if (server.isNull()) {
+        // Apparently the server knows a remoteAddress we are not aware of. There are plenty of ways
+        // to make that happen: /etc/hosts on the server, different writings for localhost,
+        // different DNS servers, ...
+        // Rather than trying to figure that out, we just use the first listening forwarder with the
+        // same port.
+        for (const SshTcpIpForwardServer::Ptr &candidate : m_listeningForwardServers) {
+            if (candidate->port() == channelOpen.remotePort) {
+                server = candidate;
+                break;
+            }
+        };
+    }
+
+    if (server.isNull()) {
+        try {
+            m_sendFacility.sendChannelOpenFailurePacket(channelOpen.common.remoteChannel,
+                                                        SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
+                                                        QByteArray());
+        }  catch (const std::exception &e) {
+            qCWarning(sshLog, "Botan error: %s", e.what());
+        }
+        return;
+    }
+
+    SshForwardedTcpIpTunnel::Ptr tunnel(new SshForwardedTcpIpTunnel(m_nextLocalChannelId++,
+                                                                    m_sendFacility));
+    tunnel->d->handleOpenSuccess(channelOpen.common.remoteChannel,
+                                 channelOpen.common.remoteWindowSize,
+                                 channelOpen.common.remoteMaxPacketSize);
+    tunnel->open(QIODevice::ReadWrite);
+    server->setNewConnection(tunnel);
+    insertChannel(tunnel->d, tunnel);
+}
+
+void SshChannelManager::handleChannelOpenX11(const SshChannelOpenGeneric &channelOpenGeneric)
+{
+    qCDebug(sshLog) << "incoming X11 channel open request";
+    const SshChannelOpenX11 channelOpen
+            = SshIncomingPacket::extractChannelOpenX11(channelOpenGeneric);
+    if (m_x11DisplayInfo.cookie.isEmpty()) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+                                   "Server attempted to open an unrequested X11 channel.");
+    }
+    SshX11Channel * const x11Channel = new SshX11Channel(m_x11DisplayInfo,
+                                                         m_nextLocalChannelId++,
+                                                         m_sendFacility);
+    x11Channel->setParent(this);
+    x11Channel->handleOpenSuccess(channelOpen.common.remoteChannel,
+                                  channelOpen.common.remoteWindowSize,
+                                  channelOpen.common.remoteMaxPacketSize);
+    insertChannel(x11Channel, QSharedPointer<QObject>());
+}
+
+int SshChannelManager::closeAllChannels(CloseAllMode mode)
+{
+    int count = 0;
+    for (ChannelIterator it = m_channels.begin(); it != m_channels.end(); ++it) {
+        AbstractSshChannel * const channel = it.value();
+        QSSH_ASSERT(channel->channelState() != AbstractSshChannel::Closed);
+        if (channel->channelState() != AbstractSshChannel::CloseRequested) {
+            channel->closeChannel();
+            ++count;
+        }
+    }
+    if (mode == CloseAllAndReset) {
+        m_channels.clear();
+        m_sessions.clear();
+    }
+    return count;
+}
+
+int SshChannelManager::channelCount() const
+{
+    return m_channels.count();
+}
+
+void SshChannelManager::removeChannel(ChannelIterator it)
+{
+    if (it == m_channels.end()) {
+        throw SshClientException(SshInternalError,
+                QLatin1String("Internal error: Unexpected channel lookup failure"));
+    }
+    const int removeCount = m_sessions.remove(it.value());
+    if (removeCount != 1) {
+        throw SshClientException(SshInternalError,
+                QString::fromLatin1("Internal error: Unexpected session count %1 for channel.")
+                                 .arg(removeCount));
+    }
+    m_channels.erase(it);
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 117 - 0
src/tool/serverctrl/qssh/sshchannelmanager_p.h

@@ -0,0 +1,117 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHCHANNELLAYER_P_H
+#define SSHCHANNELLAYER_P_H
+
+#include "sshx11displayinfo_p.h"
+
+#include <QHash>
+#include <QObject>
+#include <QSharedPointer>
+
+namespace QSsh {
+class SftpChannel;
+class SshDirectTcpIpTunnel;
+class SshRemoteProcess;
+class SshTcpIpForwardServer;
+
+namespace Internal {
+
+class AbstractSshChannel;
+struct SshChannelOpenGeneric;
+class SshIncomingPacket;
+class SshSendFacility;
+class SshRemoteProcessPrivate;
+
+class SshChannelManager : public QObject
+{
+    Q_OBJECT
+public:
+    SshChannelManager(SshSendFacility &sendFacility, QObject *parent);
+
+    QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
+    QSharedPointer<SshRemoteProcess> createRemoteShell();
+    QSharedPointer<SftpChannel> createSftpChannel();
+    QSharedPointer<SshDirectTcpIpTunnel> createDirectTunnel(const QString &originatingHost,
+            quint16 originatingPort, const QString &remoteHost, quint16 remotePort);
+    QSharedPointer<SshTcpIpForwardServer> createForwardServer(const QString &remoteHost,
+            quint16 remotePort);
+
+    int channelCount() const;
+    enum CloseAllMode { CloseAllRegular, CloseAllAndReset };
+    int closeAllChannels(CloseAllMode mode);
+    QString x11DisplayName() const { return m_x11DisplayInfo.displayName; }
+
+    void handleChannelRequest(const SshIncomingPacket &packet);
+    void handleChannelOpen(const SshIncomingPacket &packet);
+    void handleChannelOpenFailure(const SshIncomingPacket &packet);
+    void handleChannelOpenConfirmation(const SshIncomingPacket &packet);
+    void handleChannelSuccess(const SshIncomingPacket &packet);
+    void handleChannelFailure(const SshIncomingPacket &packet);
+    void handleChannelWindowAdjust(const SshIncomingPacket &packet);
+    void handleChannelData(const SshIncomingPacket &packet);
+    void handleChannelExtendedData(const SshIncomingPacket &packet);
+    void handleChannelEof(const SshIncomingPacket &packet);
+    void handleChannelClose(const SshIncomingPacket &packet);
+    void handleRequestSuccess(const SshIncomingPacket &packet);
+    void handleRequestFailure(const SshIncomingPacket &packet);
+
+signals:
+    void timeout();
+
+private:
+    typedef QHash<quint32, AbstractSshChannel *>::Iterator ChannelIterator;
+
+    ChannelIterator lookupChannelAsIterator(quint32 channelId,
+        bool allowNotFound = false);
+    AbstractSshChannel *lookupChannel(quint32 channelId,
+        bool allowNotFound = false);
+    void removeChannel(ChannelIterator it);
+    void insertChannel(AbstractSshChannel *priv,
+        const QSharedPointer<QObject> &pub);
+
+    void handleChannelOpenForwardedTcpIp(const SshChannelOpenGeneric &channelOpenGeneric);
+    void handleChannelOpenX11(const SshChannelOpenGeneric &channelOpenGeneric);
+
+    SshSendFacility &m_sendFacility;
+    QHash<quint32, AbstractSshChannel *> m_channels;
+    QHash<AbstractSshChannel *, QSharedPointer<QObject> > m_sessions;
+    quint32 m_nextLocalChannelId;
+    QList<QSharedPointer<SshTcpIpForwardServer>> m_waitingForwardServers;
+    QList<QSharedPointer<SshTcpIpForwardServer>> m_listeningForwardServers;
+    QList<SshRemoteProcessPrivate *> m_x11ForwardingRequests;
+    X11DisplayInfo m_x11DisplayInfo;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHCHANNELLAYER_P_H

+ 1035 - 0
src/tool/serverctrl/qssh/sshconnection.cpp

@@ -0,0 +1,1035 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshconnection.h"
+#include "sshconnection_p.h"
+
+#include "sftpchannel.h"
+#include "sshagent_p.h"
+#include "sshcapabilities_p.h"
+#include "sshchannelmanager_p.h"
+#include "sshcryptofacility_p.h"
+#include "sshdirecttcpiptunnel.h"
+#include "sshtcpipforwardserver.h"
+#include "sshexception_p.h"
+#include "sshkeyexchange_p.h"
+#include "sshremoteprocess.h"
+#include "sshlogging_p.h"
+
+#include <QFile>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QNetworkProxy>
+#include <QRegularExpression>
+#include <QTcpSocket>
+
+namespace QSsh {
+
+namespace {
+const QByteArray ClientId("SSH-2.0-QtCreator\r\n");
+}
+
+SshConnectionParameters::SshConnectionParameters() :
+    timeout(0), authenticationType(AuthenticationTypePublicKey),
+    hostKeyCheckingMode(SshHostKeyCheckingNone)
+{
+    url.setPort(0);
+    options |= SshIgnoreDefaultProxy;
+    hostKeyDatabase = SshHostKeyDatabasePtr(new SshHostKeyDatabase);
+}
+
+static inline bool equals(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
+{
+    return p1.url == p2.url
+            && p1.authenticationType == p2.authenticationType
+            && p1.privateKeyFile == p2.privateKeyFile
+            && p1.hostKeyCheckingMode == p2.hostKeyCheckingMode
+            && p1.timeout == p2.timeout;
+}
+
+bool operator==(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
+{
+    return equals(p1, p2);
+}
+
+bool operator!=(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
+{
+    return !equals(p1, p2);
+}
+
+SshConnection::SshConnection(const SshConnectionParameters &serverInfo, QObject *parent)
+    : QObject(parent)
+{
+    qRegisterMetaType<QSsh::SshError>("QSsh::SshError");
+    qRegisterMetaType<QSsh::SftpJobId>("QSsh::SftpJobId");
+    qRegisterMetaType<QSsh::SftpFileInfo>("QSsh::SftpFileInfo");
+    qRegisterMetaType<QSsh::SftpError>("QSsh::SftpError");
+    qRegisterMetaType<QSsh::SftpError>("SftpError");
+    qRegisterMetaType<QList <QSsh::SftpFileInfo> >("QList<QSsh::SftpFileInfo>");
+
+    d = new Internal::SshConnectionPrivate(this, serverInfo);
+    connect(d, &Internal::SshConnectionPrivate::connected, this, &SshConnection::connected,
+        Qt::QueuedConnection);
+    connect(d, &Internal::SshConnectionPrivate::dataAvailable, this,
+        &SshConnection::dataAvailable, Qt::QueuedConnection);
+    connect(d, &Internal::SshConnectionPrivate::disconnected, this, &SshConnection::disconnected,
+        Qt::QueuedConnection);
+    connect(d, &Internal::SshConnectionPrivate::error, this,
+            &SshConnection::error, Qt::QueuedConnection);
+}
+
+const QByteArray &SshConnection::hostKeyFingerprint() const
+{
+    return d->hostKeyFingerprint();
+}
+
+void SshConnection::connectToHost()
+{
+    d->connectToHost();
+}
+
+void SshConnection::disconnectFromHost()
+{
+    d->closeConnection(Internal::SSH_DISCONNECT_BY_APPLICATION, SshNoError, "",
+        QString());
+}
+
+SshConnection::State SshConnection::state() const
+{
+    switch (d->state()) {
+    case Internal::SocketUnconnected:
+        return Unconnected;
+    case Internal::ConnectionEstablished:
+        return Connected;
+    default:
+        return Connecting;
+    }
+}
+
+SshError SshConnection::errorState() const
+{
+    return d->errorState();
+}
+
+QString SshConnection::errorString() const
+{
+    return d->errorString();
+}
+
+SshConnectionParameters SshConnection::connectionParameters() const
+{
+    return d->m_connParams;
+}
+
+SshConnectionInfo SshConnection::connectionInfo() const
+{
+    QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshConnectionInfo());
+
+    return SshConnectionInfo(d->m_socket->localAddress(), d->m_socket->localPort(),
+        d->m_socket->peerAddress(), d->m_socket->peerPort());
+}
+
+SshConnection::~SshConnection()
+{
+    disconnect();
+    disconnectFromHost();
+    delete d;
+}
+
+QSharedPointer<SshRemoteProcess> SshConnection::createRemoteProcess(const QByteArray &command)
+{
+    QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SshRemoteProcess>());
+    return d->createRemoteProcess(command);
+}
+
+QSharedPointer<SshRemoteProcess> SshConnection::createRemoteShell()
+{
+    QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SshRemoteProcess>());
+    return d->createRemoteShell();
+}
+
+QSharedPointer<SftpChannel> SshConnection::createSftpChannel()
+{
+    QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SftpChannel>());
+    return d->createSftpChannel();
+}
+
+SshDirectTcpIpTunnel::Ptr SshConnection::createDirectTunnel(const QString &originatingHost,
+        quint16 originatingPort, const QString &remoteHost, quint16 remotePort)
+{
+    QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshDirectTcpIpTunnel::Ptr());
+    return d->createDirectTunnel(originatingHost, originatingPort, remoteHost, remotePort);
+}
+
+QSharedPointer<SshTcpIpForwardServer> SshConnection::createForwardServer(const QString &remoteHost,
+        quint16 remotePort)
+{
+    QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshTcpIpForwardServer::Ptr());
+    return d->createForwardServer(remoteHost, remotePort);
+}
+
+int SshConnection::closeAllChannels()
+{
+    try {
+        return d->m_channelManager->closeAllChannels(Internal::SshChannelManager::CloseAllRegular);
+    } catch (const std::exception &e) {
+        qCWarning(Internal::sshLog, "%s: %s", Q_FUNC_INFO, e.what());
+        return -1;
+    }
+}
+
+int SshConnection::channelCount() const
+{
+    return d->m_channelManager->channelCount();
+}
+
+QString SshConnection::x11DisplayName() const
+{
+    return d->m_channelManager->x11DisplayName();
+}
+
+namespace Internal {
+
+SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn,
+    const SshConnectionParameters &serverInfo)
+    : m_socket(new QTcpSocket(this)), m_state(SocketUnconnected),
+      m_sendFacility(m_socket),
+      m_channelManager(new SshChannelManager(m_sendFacility, this)),
+      m_connParams(serverInfo), m_error(SshNoError), m_ignoreNextPacket(false),
+      m_conn(conn)
+{
+    setupPacketHandlers();
+
+    if (m_connParams.options & SshLowDelaySocket) {
+        m_socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
+    }
+
+    m_socket->setProxy((m_connParams.options & SshIgnoreDefaultProxy)
+            ? QNetworkProxy::NoProxy : QNetworkProxy::DefaultProxy);
+    m_timeoutTimer.setTimerType(Qt::VeryCoarseTimer);
+    m_timeoutTimer.setSingleShot(true);
+    m_timeoutTimer.setInterval(m_connParams.timeout * 1000);
+    m_keepAliveTimer.setTimerType(Qt::VeryCoarseTimer);
+    m_keepAliveTimer.setSingleShot(true);
+    m_keepAliveTimer.setInterval(10000);
+    connect(m_channelManager, &SshChannelManager::timeout,
+            this, &SshConnectionPrivate::handleTimeout);
+}
+
+SshConnectionPrivate::~SshConnectionPrivate()
+{
+    disconnect();
+}
+
+void SshConnectionPrivate::setupPacketHandlers()
+{
+    typedef SshConnectionPrivate This;
+
+    setupPacketHandler(SSH_MSG_KEXINIT, StateList() << SocketConnected
+        << ConnectionEstablished, &This::handleKeyExchangeInitPacket);
+    setupPacketHandler(SSH_MSG_KEXDH_REPLY, StateList() << SocketConnected
+        << ConnectionEstablished, &This::handleKeyExchangeReplyPacket);
+
+    setupPacketHandler(SSH_MSG_NEWKEYS, StateList() << SocketConnected
+        << ConnectionEstablished, &This::handleNewKeysPacket);
+    setupPacketHandler(SSH_MSG_SERVICE_ACCEPT,
+        StateList() << UserAuthServiceRequested,
+        &This::handleServiceAcceptPacket);
+    if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePassword
+            || m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods) {
+        setupPacketHandler(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
+                StateList() << UserAuthRequested, &This::handlePasswordExpiredPacket);
+    }
+    setupPacketHandler(SSH_MSG_GLOBAL_REQUEST,
+        StateList() << ConnectionEstablished, &This::handleGlobalRequest);
+
+    const StateList authReqList = StateList() << UserAuthRequested;
+    setupPacketHandler(SSH_MSG_USERAUTH_BANNER, authReqList,
+        &This::handleUserAuthBannerPacket);
+    setupPacketHandler(SSH_MSG_USERAUTH_SUCCESS, authReqList,
+        &This::handleUserAuthSuccessPacket);
+    setupPacketHandler(SSH_MSG_USERAUTH_FAILURE, authReqList,
+        &This::handleUserAuthFailurePacket);
+    if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeKeyboardInteractive
+            || m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods) {
+        setupPacketHandler(SSH_MSG_USERAUTH_INFO_REQUEST, authReqList,
+                &This::handleUserAuthInfoRequestPacket);
+    }
+    setupPacketHandler(SSH_MSG_USERAUTH_PK_OK, authReqList, &This::handleUserAuthKeyOkPacket);
+
+    const StateList connectedList
+        = StateList() << ConnectionEstablished;
+    setupPacketHandler(SSH_MSG_CHANNEL_REQUEST, connectedList,
+        &This::handleChannelRequest);
+    setupPacketHandler(SSH_MSG_CHANNEL_OPEN, connectedList,
+        &This::handleChannelOpen);
+    setupPacketHandler(SSH_MSG_CHANNEL_OPEN_FAILURE, connectedList,
+        &This::handleChannelOpenFailure);
+    setupPacketHandler(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, connectedList,
+        &This::handleChannelOpenConfirmation);
+    setupPacketHandler(SSH_MSG_CHANNEL_SUCCESS, connectedList,
+        &This::handleChannelSuccess);
+    setupPacketHandler(SSH_MSG_CHANNEL_FAILURE, connectedList,
+        &This::handleChannelFailure);
+    setupPacketHandler(SSH_MSG_CHANNEL_WINDOW_ADJUST, connectedList,
+        &This::handleChannelWindowAdjust);
+    setupPacketHandler(SSH_MSG_CHANNEL_DATA, connectedList,
+        &This::handleChannelData);
+    setupPacketHandler(SSH_MSG_CHANNEL_EXTENDED_DATA, connectedList,
+        &This::handleChannelExtendedData);
+
+    const StateList connectedOrClosedList
+        = StateList() << SocketUnconnected << ConnectionEstablished;
+    setupPacketHandler(SSH_MSG_CHANNEL_EOF, connectedOrClosedList,
+        &This::handleChannelEof);
+    setupPacketHandler(SSH_MSG_CHANNEL_CLOSE, connectedOrClosedList,
+        &This::handleChannelClose);
+
+    setupPacketHandler(SSH_MSG_DISCONNECT, StateList() << SocketConnected << WaitingForAgentKeys
+        << UserAuthServiceRequested << UserAuthRequested
+        << ConnectionEstablished, &This::handleDisconnect);
+
+    setupPacketHandler(SSH_MSG_UNIMPLEMENTED,
+        StateList() << ConnectionEstablished, &This::handleUnimplementedPacket);
+
+    setupPacketHandler(SSH_MSG_REQUEST_SUCCESS, connectedList,
+        &This::handleRequestSuccess);
+    setupPacketHandler(SSH_MSG_REQUEST_FAILURE, connectedList,
+        &This::handleRequestFailure);
+}
+
+void SshConnectionPrivate::setupPacketHandler(SshPacketType type,
+    const SshConnectionPrivate::StateList &states,
+    SshConnectionPrivate::PacketHandler handler)
+{
+    m_packetHandlers.insert(type, HandlerInStates(states, handler));
+}
+
+void SshConnectionPrivate::handleSocketConnected()
+{
+    m_state = SocketConnected;
+    sendData(ClientId);
+}
+
+void SshConnectionPrivate::handleIncomingData()
+{
+    if (m_state == SocketUnconnected)
+        return; // For stuff queued in the event loop after we've called closeConnection();
+
+    try {
+        if (!canUseSocket())
+            return;
+        m_incomingData += m_socket->readAll();
+        qCDebug(sshLog, "state = %d, remote data size = %d", int(m_state), int(m_incomingData.count()));
+        if (m_serverId.isEmpty())
+            handleServerId();
+        handlePackets();
+    } catch (const SshServerException &e) {
+        closeConnection(e.error, SshProtocolError, e.errorStringServer,
+            tr("SSH Protocol error: %1").arg(e.errorStringUser));
+    } catch (const SshClientException &e) {
+        closeConnection(SSH_DISCONNECT_BY_APPLICATION, e.error, "",
+            e.errorString);
+    } catch (const std::exception &e) {
+        closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshInternalError, "",
+            tr("Botan library exception: %1").arg(QString::fromLocal8Bit(e.what())));
+    }
+}
+
+// RFC 4253, 4.2.
+void SshConnectionPrivate::handleServerId()
+{
+    qCDebug(sshLog, "%s: incoming data size = %d, incoming data = '%s'",
+        Q_FUNC_INFO, int(m_incomingData.count()), m_incomingData.data());
+    const int newLinePos = m_incomingData.indexOf('\n');
+    if (newLinePos == -1)
+        return; // Not enough data yet.
+
+    // Lines not starting with "SSH-" are ignored.
+    if (!m_incomingData.startsWith("SSH-")) {
+        m_incomingData.remove(0, newLinePos + 1);
+        m_serverHasSentDataBeforeId = true;
+        return;
+    }
+
+    if (newLinePos > 255 - 1) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Identification string too long.",
+            tr("Server identification string is %1 characters long, but the maximum "
+               "allowed length is 255.").arg(newLinePos + 1));
+    }
+
+    const bool hasCarriageReturn = m_incomingData.at(newLinePos - 1) == '\r';
+    m_serverId = m_incomingData.left(newLinePos);
+    if (hasCarriageReturn)
+        m_serverId.chop(1);
+    m_incomingData.remove(0, newLinePos + 1);
+
+    if (m_serverId.contains('\0')) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Identification string contains illegal NUL character.",
+            tr("Server identification string contains illegal NUL character."));
+    }
+
+    // "printable US-ASCII characters, with the exception of whitespace characters
+    // and the minus sign"
+    QString legalString = QLatin1String("[]!\"#$!&'()*+,./0-9:;<=>?@A-Z[\\\\^_`a-z{|}~]+");
+    const QRegularExpression versionIdpattern(QString::fromLatin1("^SSH-(%1)-%1(?: .+)?.*$").arg(legalString));
+    const QRegularExpressionMatch match = versionIdpattern.match(QString::fromLatin1(m_serverId));
+    if (!match.hasMatch()) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Identification string is invalid.",
+            tr("Server Identification string \"%1\" is invalid.")
+                    .arg(QString::fromLatin1(m_serverId)));
+    }
+    const QString serverProtoVersion = match.captured(1);
+    if (serverProtoVersion != QLatin1String("2.0") && serverProtoVersion != QLatin1String("1.99")) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
+            "Invalid protocol version.",
+            tr("Server protocol version is \"%1\", but needs to be 2.0 or 1.99.")
+                    .arg(serverProtoVersion));
+    }
+
+    if (serverProtoVersion == QLatin1String("2.0") && !hasCarriageReturn) {
+        if (m_connParams.options & SshEnableStrictConformanceChecks) {
+            throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+                    "Identification string is invalid.",
+                    tr("Server identification string is invalid (missing carriage return)."));
+        } else {
+            qCWarning(Internal::sshLog, "Server identification string is invalid (missing carriage return).");
+        }
+    }
+
+    if (serverProtoVersion == QLatin1String("1.99") && m_serverHasSentDataBeforeId) {
+        if (m_connParams.options & SshEnableStrictConformanceChecks) {
+            throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+                    "No extra data preceding identification string allowed for 1.99.",
+                    tr("Server reports protocol version 1.99, but sends data "
+                        "before the identification string, which is not allowed."));
+        } else {
+            qCWarning(Internal::sshLog, "Server reports protocol version 1.99, but sends data "
+                        "before the identification string, which is not allowed.");
+        }
+    }
+
+    m_keyExchange.reset(new SshKeyExchange(m_connParams, m_sendFacility));
+    m_keyExchange->sendKexInitPacket(m_serverId);
+    m_keyExchangeState = KexInitSent;
+}
+
+void SshConnectionPrivate::handlePackets()
+{
+    m_incomingPacket.consumeData(m_incomingData);
+    while (m_incomingPacket.isComplete()) {
+        handleCurrentPacket();
+        m_incomingPacket.clear();
+        m_incomingPacket.consumeData(m_incomingData);
+    }
+}
+
+void SshConnectionPrivate::handleCurrentPacket()
+{
+    Q_ASSERT(m_incomingPacket.isComplete());
+    Q_ASSERT(m_keyExchangeState == DhInitSent || !m_ignoreNextPacket);
+
+    if (m_ignoreNextPacket) {
+        m_ignoreNextPacket = false;
+        return;
+    }
+
+    QHash<SshPacketType, HandlerInStates>::ConstIterator it
+        = m_packetHandlers.constFind(m_incomingPacket.type());
+    if (it == m_packetHandlers.constEnd()) {
+        m_sendFacility.sendMsgUnimplementedPacket(m_incomingPacket.serverSeqNr());
+        return;
+    }
+    if (!it.value().first.contains(m_state)) {
+        handleUnexpectedPacket();
+        return;
+    }
+    (this->*it.value().second)();
+}
+
+void SshConnectionPrivate::handleKeyExchangeInitPacket()
+{
+    if (m_keyExchangeState != NoKeyExchange
+            && m_keyExchangeState != KexInitSent) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected packet.", tr("Unexpected packet of type %1.")
+            .arg(m_incomingPacket.type()));
+    }
+
+    // Server-initiated re-exchange.
+    if (m_keyExchangeState == NoKeyExchange) {
+        m_keyExchange.reset(new SshKeyExchange(m_connParams, m_sendFacility));
+        m_keyExchange->sendKexInitPacket(m_serverId);
+    }
+
+    // If the server sends a guessed packet, the guess must be wrong,
+    // because the algorithms we support require us to initiate the
+    // key exchange.
+    if (m_keyExchange->sendDhInitPacket(m_incomingPacket)) {
+        m_ignoreNextPacket = true;
+    }
+
+    m_keyExchangeState = DhInitSent;
+}
+
+void SshConnectionPrivate::handleKeyExchangeReplyPacket()
+{
+    if (m_keyExchangeState != DhInitSent) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected packet.", tr("Unexpected packet of type %1.")
+            .arg(m_incomingPacket.type()));
+    }
+
+    m_keyExchange->sendNewKeysPacket(m_incomingPacket,
+        ClientId.left(ClientId.size() - 2));
+    m_hostFingerprint = m_keyExchange->hostKeyFingerprint();
+    m_sendFacility.recreateKeys(*m_keyExchange);
+    m_keyExchangeState = NewKeysSent;
+}
+
+void SshConnectionPrivate::handleNewKeysPacket()
+{
+    if (m_keyExchangeState != NewKeysSent) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected packet.", tr("Unexpected packet of type %1.")
+            .arg(m_incomingPacket.type()));
+    }
+
+    m_incomingPacket.recreateKeys(*m_keyExchange);
+    m_keyExchange.reset();
+    m_keyExchangeState = NoKeyExchange;
+
+    if (m_state == SocketConnected) {
+        m_sendFacility.sendUserAuthServiceRequestPacket();
+        m_state = UserAuthServiceRequested;
+    }
+}
+
+void SshConnectionPrivate::handleServiceAcceptPacket()
+{
+    switch (m_connParams.authenticationType) {
+    case SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods:
+        m_triedAllPasswordBasedMethods = false;
+        Q_FALLTHROUGH();
+    case SshConnectionParameters::AuthenticationTypePassword:
+        m_sendFacility.sendUserAuthByPasswordRequestPacket(m_connParams.userName().toUtf8(),
+                SshCapabilities::SshConnectionService, m_connParams.password().toUtf8());
+        break;
+    case SshConnectionParameters::AuthenticationTypeKeyboardInteractive:
+        m_sendFacility.sendUserAuthByKeyboardInteractiveRequestPacket(m_connParams.userName().toUtf8(),
+                SshCapabilities::SshConnectionService);
+        break;
+    case SshConnectionParameters::AuthenticationTypePublicKey:
+        authenticateWithPublicKey();
+        break;
+    case SshConnectionParameters::AuthenticationTypeAgent:
+        if (SshAgent::publicKeys().isEmpty()) {
+            if (m_agentKeysUpToDate)
+                throw SshClientException(SshAuthenticationError, tr("ssh-agent has no keys."));
+            qCDebug(sshLog) << "agent has no keys yet, waiting";
+            m_state = WaitingForAgentKeys;
+            return;
+        } else {
+            tryAllAgentKeys();
+        }
+        break;
+    }
+    m_state = UserAuthRequested;
+}
+
+void SshConnectionPrivate::handlePasswordExpiredPacket()
+{
+    if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
+            && m_triedAllPasswordBasedMethods) {
+        // This means we just tried to authorize via "keyboard-interactive", in which case
+        // this type of packet is not allowed.
+        handleUnexpectedPacket();
+        return;
+    }
+    throw SshClientException(SshAuthenticationError, tr("Password expired."));
+}
+
+void SshConnectionPrivate::handleUserAuthInfoRequestPacket()
+{
+    if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
+            && !m_triedAllPasswordBasedMethods) {
+        // This means we just tried to authorize via "password", in which case
+        // this type of packet is not allowed.
+        handleUnexpectedPacket();
+        return;
+    }
+
+    const SshUserAuthInfoRequestPacket requestPacket
+            = m_incomingPacket.extractUserAuthInfoRequest();
+    QStringList responses;
+    responses.reserve(requestPacket.prompts.count());
+
+    // Not very interactive, admittedly, but we don't want to be for now.
+    for (int i = 0;  i < requestPacket.prompts.count(); ++i)
+        responses << m_connParams.password();
+    m_sendFacility.sendUserAuthInfoResponsePacket(responses);
+}
+
+void SshConnectionPrivate::handleUserAuthBannerPacket()
+{
+    emit dataAvailable(m_incomingPacket.extractUserAuthBanner().message);
+}
+
+void SshConnectionPrivate::handleUnexpectedPacket()
+{
+    throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+                             "Unexpected packet.", tr("Unexpected packet of type %1.")
+                             .arg(m_incomingPacket.type()));
+}
+
+void SshConnectionPrivate::handleGlobalRequest()
+{
+    m_sendFacility.sendRequestFailurePacket();
+}
+
+void SshConnectionPrivate::handleUserAuthSuccessPacket()
+{
+    m_state = ConnectionEstablished;
+    m_timeoutTimer.stop();
+    emit connected();
+    m_lastInvalidMsgSeqNr = InvalidSeqNr;
+    connect(&m_keepAliveTimer, &QTimer::timeout, this, &SshConnectionPrivate::sendKeepAlivePacket);
+    m_keepAliveTimer.start();
+}
+
+void SshConnectionPrivate::handleUserAuthFailurePacket()
+{
+    if (!m_pendingKeyChecks.isEmpty()) {
+        const QByteArray key = m_pendingKeyChecks.dequeue();
+        SshAgent::removeDataToSign(key, tokenForAgent());
+        qCDebug(sshLog) << "server rejected one of the keys supplied by the agent,"
+                        << m_pendingKeyChecks.count() << "keys remaining";
+        if (m_pendingKeyChecks.isEmpty() && m_agentKeyToUse.isEmpty()) {
+            throw SshClientException(SshAuthenticationError, tr("The server rejected all keys "
+                                                                "known to the ssh-agent."));
+        }
+        return;
+    }
+
+    // TODO: Evaluate "authentications that can continue" field and act on it.
+    if (m_connParams.authenticationType
+            == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
+        && !m_triedAllPasswordBasedMethods) {
+        m_triedAllPasswordBasedMethods = true;
+        m_sendFacility.sendUserAuthByKeyboardInteractiveRequestPacket(
+                    m_connParams.userName().toUtf8(),
+                    SshCapabilities::SshConnectionService);
+        return;
+    }
+
+    m_timeoutTimer.stop();
+    QString errorMsg;
+    switch (m_connParams.authenticationType) {
+    case SshConnectionParameters::AuthenticationTypePublicKey:
+    case SshConnectionParameters::AuthenticationTypeAgent:
+        errorMsg = tr("Server rejected key.");
+        break;
+    default:
+        errorMsg = tr("Server rejected password.");
+        break;
+    }
+    throw SshClientException(SshAuthenticationError, errorMsg);
+}
+
+void SshConnectionPrivate::handleUserAuthKeyOkPacket()
+{
+    const SshUserAuthPkOkPacket &msg = m_incomingPacket.extractUserAuthPkOk();
+    qCDebug(sshLog) << "server accepted key of type" << msg.algoName;
+
+    if (m_pendingKeyChecks.isEmpty()) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, "Unexpected packet",
+                                 tr("Server sent unexpected SSH_MSG_USERAUTH_PK_OK packet."));
+    }
+    const QByteArray key = m_pendingKeyChecks.dequeue();
+    if (key != msg.keyBlob) {
+        // The server must answer the requests in the order we sent them.
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, "Unexpected packet content",
+                tr("Server sent unexpected key in SSH_MSG_USERAUTH_PK_OK packet."));
+    }
+    const uint token = tokenForAgent();
+    if (!m_agentKeyToUse.isEmpty()) {
+        qCDebug(sshLog) << "another key has already been accepted, ignoring this one";
+        SshAgent::removeDataToSign(key, token);
+        return;
+    }
+    m_agentKeyToUse = key;
+    qCDebug(sshLog) << "requesting signature from agent";
+    SshAgent::requestSignature(key, token);
+}
+
+void SshConnectionPrivate::handleDebugPacket()
+{
+    const SshDebug &msg = m_incomingPacket.extractDebug();
+    if (msg.display)
+        emit dataAvailable(msg.message);
+}
+
+void SshConnectionPrivate::handleUnimplementedPacket()
+{
+    const SshUnimplemented &msg = m_incomingPacket.extractUnimplemented();
+    if (msg.invalidMsgSeqNr != m_lastInvalidMsgSeqNr) {
+        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected packet", tr("The server sent an unexpected SSH packet "
+            "of type SSH_MSG_UNIMPLEMENTED."));
+    }
+    m_lastInvalidMsgSeqNr = InvalidSeqNr;
+    m_timeoutTimer.stop();
+    m_keepAliveTimer.start();
+}
+
+void SshConnectionPrivate::handleChannelRequest()
+{
+    m_channelManager->handleChannelRequest(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelOpen()
+{
+    m_channelManager->handleChannelOpen(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelOpenFailure()
+{
+   m_channelManager->handleChannelOpenFailure(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelOpenConfirmation()
+{
+    m_channelManager->handleChannelOpenConfirmation(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelSuccess()
+{
+    m_channelManager->handleChannelSuccess(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelFailure()
+{
+    m_channelManager->handleChannelFailure(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelWindowAdjust()
+{
+   m_channelManager->handleChannelWindowAdjust(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelData()
+{
+   m_channelManager->handleChannelData(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelExtendedData()
+{
+   m_channelManager->handleChannelExtendedData(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelEof()
+{
+   m_channelManager->handleChannelEof(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelClose()
+{
+   m_channelManager->handleChannelClose(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleDisconnect()
+{
+    const SshDisconnect msg = m_incomingPacket.extractDisconnect();
+    throw SshServerException(SSH_DISCONNECT_CONNECTION_LOST,
+        "", tr("Server closed connection: %1").arg(msg.description));
+}
+
+void SshConnectionPrivate::handleRequestSuccess()
+{
+    m_channelManager->handleRequestSuccess(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleRequestFailure()
+{
+    m_channelManager->handleRequestFailure(m_incomingPacket);
+}
+
+void SshConnectionPrivate::sendData(const QByteArray &data)
+{
+    if (canUseSocket())
+        m_socket->write(data);
+}
+
+uint SshConnectionPrivate::tokenForAgent() const
+{
+    return qHash(m_sendFacility.sessionId());
+}
+
+void SshConnectionPrivate::handleSocketDisconnected()
+{
+    closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError,
+        "Connection closed unexpectedly.",
+        tr("Connection closed unexpectedly."));
+}
+
+void SshConnectionPrivate::handleSocketError()
+{
+    if (m_error == SshNoError) {
+        closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshSocketError,
+            "Network error", m_socket->errorString());
+    }
+}
+
+void SshConnectionPrivate::handleTimeout()
+{
+    const QString errorMessage = m_state == WaitingForAgentKeys
+            ? tr("Timeout waiting for keys from ssh-agent.")
+            : tr("Timeout waiting for reply from server.");
+    closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "", errorMessage);
+}
+
+void SshConnectionPrivate::sendKeepAlivePacket()
+{
+    // This type of message is not allowed during key exchange.
+    if (m_keyExchangeState != NoKeyExchange) {
+        m_keepAliveTimer.start();
+        return;
+    }
+
+    Q_ASSERT(m_lastInvalidMsgSeqNr == InvalidSeqNr);
+    m_lastInvalidMsgSeqNr = m_sendFacility.nextClientSeqNr();
+    m_sendFacility.sendInvalidPacket();
+    m_timeoutTimer.start();
+}
+
+void SshConnectionPrivate::handleAgentKeysUpdated()
+{
+    m_agentKeysUpToDate = true;
+    if (m_state == WaitingForAgentKeys) {
+        m_state = UserAuthRequested;
+        tryAllAgentKeys();
+    }
+}
+
+void SshConnectionPrivate::handleSignatureFromAgent(const QByteArray &key,
+                                                    const QByteArray &signature, uint token)
+{
+    if (token != tokenForAgent()) {
+        qCDebug(sshLog) << "signature is for different connection, ignoring";
+        return;
+    }
+    QSSH_ASSERT(key == m_agentKeyToUse);
+    m_agentSignature = signature;
+    authenticateWithPublicKey();
+}
+
+void SshConnectionPrivate::tryAllAgentKeys()
+{
+    const QList<QByteArray> &keys = SshAgent::publicKeys();
+    if (keys.isEmpty())
+        throw SshClientException(SshAuthenticationError, tr("ssh-agent has no keys."));
+    qCDebug(sshLog) << "trying authentication with" << keys.count()
+                    << "public keys received from agent";
+    for (const QByteArray &key : keys) {
+        m_sendFacility.sendQueryPublicKeyPacket(m_connParams.userName().toUtf8(),
+                                                SshCapabilities::SshConnectionService, key);
+        m_pendingKeyChecks.enqueue(key);
+    }
+}
+
+void SshConnectionPrivate::authenticateWithPublicKey()
+{
+    qCDebug(sshLog) << "sending actual authentication request";
+
+    QByteArray key;
+    QByteArray signature;
+    if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeAgent) {
+        // Agent is not needed anymore after this point.
+        disconnect(&SshAgent::instance(), nullptr, this, nullptr);
+
+        key = m_agentKeyToUse;
+        signature = m_agentSignature;
+    }
+
+    m_sendFacility.sendUserAuthByPublicKeyRequestPacket(m_connParams.userName().toUtf8(),
+            SshCapabilities::SshConnectionService, key, signature);
+}
+
+void SshConnectionPrivate::setAgentError()
+{
+    m_error = SshAgentError;
+    m_errorString = SshAgent::errorString();
+    emit error(m_error);
+}
+
+void SshConnectionPrivate::connectToHost()
+{
+    QSSH_ASSERT_AND_RETURN(m_state == SocketUnconnected);
+
+    m_incomingData.clear();
+    m_incomingPacket.reset();
+    m_sendFacility.reset();
+    m_error = SshNoError;
+    m_ignoreNextPacket = false;
+    m_errorString.clear();
+    m_serverId.clear();
+    m_serverHasSentDataBeforeId = false;
+    m_agentSignature.clear();
+    m_agentKeysUpToDate = false;
+    m_pendingKeyChecks.clear();
+    m_agentKeyToUse.clear();
+
+    switch (m_connParams.authenticationType) {
+    case SshConnectionParameters::AuthenticationTypePublicKey:
+        try {
+            createPrivateKey();
+            break;
+        } catch (const SshClientException &ex) {
+            m_error = ex.error;
+            m_errorString = ex.errorString;
+            emit error(m_error);
+            return;
+        }
+    case SshConnectionParameters::AuthenticationTypeAgent:
+        if (SshAgent::hasError()) {
+            setAgentError();
+            return;
+        }
+        connect(&SshAgent::instance(), &SshAgent::errorOccurred,
+                this, &SshConnectionPrivate::setAgentError);
+        connect(&SshAgent::instance(), &SshAgent::keysUpdated,
+                this, &SshConnectionPrivate::handleAgentKeysUpdated);
+        SshAgent::refreshKeys();
+        connect(&SshAgent::instance(), &SshAgent::signatureAvailable,
+                this, &SshConnectionPrivate::handleSignatureFromAgent);
+        break;
+    default:
+        break;
+    }
+
+    connect(m_socket, &QAbstractSocket::connected,
+            this, &SshConnectionPrivate::handleSocketConnected);
+    connect(m_socket, &QIODevice::readyRead,
+            this, &SshConnectionPrivate::handleIncomingData);
+//    connect(m_socket, &QAbstractSocket::errorOccurred, this, &SshConnectionPrivate::handleSocketError);
+    connect(m_socket, &QAbstractSocket::disconnected,
+            this, &SshConnectionPrivate::handleSocketDisconnected);
+    connect(&m_timeoutTimer, &QTimer::timeout, this, &SshConnectionPrivate::handleTimeout);
+    m_state = SocketConnecting;
+    m_keyExchangeState = NoKeyExchange;
+    m_timeoutTimer.start();
+    m_socket->connectToHost(m_connParams.host(), m_connParams.port());
+}
+
+void SshConnectionPrivate::closeConnection(SshErrorCode sshError,
+    SshError userError, const QByteArray &serverErrorString,
+    const QString &userErrorString)
+{
+    // Prevent endless loops by recursive exceptions.
+    if (m_state == SocketUnconnected || m_error != SshNoError)
+        return;
+
+    m_error = userError;
+    m_errorString = userErrorString;
+    m_timeoutTimer.stop();
+    disconnect(m_socket, nullptr, this, nullptr);
+    disconnect(&m_timeoutTimer, nullptr, this, nullptr);
+    m_keepAliveTimer.stop();
+    disconnect(&m_keepAliveTimer, nullptr, this, nullptr);
+    try {
+        m_channelManager->closeAllChannels(SshChannelManager::CloseAllAndReset);
+
+        // Crypto initialization failed
+        if (m_sendFacility.encrypterIsValid()) {
+            m_sendFacility.sendDisconnectPacket(sshError, serverErrorString);
+        }
+    } catch (...) {}  // Nothing sensible to be done here.
+    if (m_error != SshNoError)
+        emit error(userError);
+    if (m_state == ConnectionEstablished)
+        emit disconnected();
+    if (canUseSocket())
+        m_socket->disconnectFromHost();
+    m_state = SocketUnconnected;
+}
+
+bool SshConnectionPrivate::canUseSocket() const
+{
+    return m_socket->isValid()
+            && m_socket->state() == QAbstractSocket::ConnectedState;
+}
+
+void SshConnectionPrivate::createPrivateKey()
+{
+    if (m_connParams.privateKeyFile.isEmpty())
+        throw SshClientException(SshKeyFileError, tr("No private key file given."));
+    QFile keyFile(m_connParams.privateKeyFile);
+    if (!keyFile.open(QIODevice::ReadOnly)) {
+        throw SshClientException(SshKeyFileError,
+            tr("Private key file error: %1").arg(keyFile.errorString()));
+    }
+    m_sendFacility.createAuthenticationKey(keyFile.readAll());
+}
+
+QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)
+{
+    return m_channelManager->createRemoteProcess(command);
+}
+
+QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteShell()
+{
+    return m_channelManager->createRemoteShell();
+}
+
+QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
+{
+    return m_channelManager->createSftpChannel();
+}
+
+SshDirectTcpIpTunnel::Ptr SshConnectionPrivate::createDirectTunnel(const QString &originatingHost,
+        quint16 originatingPort, const QString &remoteHost, quint16 remotePort)
+{
+    return m_channelManager->createDirectTunnel(originatingHost, originatingPort, remoteHost,
+                                                remotePort);
+}
+
+SshTcpIpForwardServer::Ptr SshConnectionPrivate::createForwardServer(const QString &bindAddress,
+        quint16 bindPort)
+{
+    return m_channelManager->createForwardServer(bindAddress, bindPort);
+}
+
+const quint64 SshConnectionPrivate::InvalidSeqNr = static_cast<quint64>(-1);
+
+} // namespace Internal
+} // namespace QSsh

+ 298 - 0
src/tool/serverctrl/qssh/sshconnection.h

@@ -0,0 +1,298 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHCONNECTION_H
+#define SSHCONNECTION_H
+
+#include "ssherrors.h"
+#include "sshhostkeydatabase.h"
+
+#include "ssh_global.h"
+
+#include <QByteArray>
+#include <QFlags>
+#include <QMetaType>
+#include <QObject>
+#include <QSharedPointer>
+#include <QString>
+#include <QHostAddress>
+#include <QUrl>
+
+namespace QSsh {
+class SftpChannel;
+class SshDirectTcpIpTunnel;
+class SshRemoteProcess;
+class SshTcpIpForwardServer;
+
+namespace Internal {
+class SshConnectionPrivate;
+} // namespace Internal
+
+/*!
+ * \brief Flags that control various general behavior
+ */
+enum SshConnectionOption {
+    /// Set this to ignore the system defined proxy
+    SshIgnoreDefaultProxy = 0x1,
+
+    /// Fail instead of warn if the remote host violates the standard
+    SshEnableStrictConformanceChecks = 0x2,
+
+    /// Set the QAbstractSocket::LowDelayOption, which is the same as TCP_NODELAY
+    SshLowDelaySocket = 0x4
+};
+
+Q_DECLARE_FLAGS(SshConnectionOptions, SshConnectionOption)
+
+/*!
+ * \brief How strict to be when checking the remote key
+ */
+enum SshHostKeyCheckingMode {
+    /// Ignore the remote key
+    SshHostKeyCheckingNone,
+
+    /// Fail connection if either there is no key stored for this host or the key is not the same as earlier
+    SshHostKeyCheckingStrict,
+
+    /// Allow connecting if there is no stored key for the host, but fail if the key has changed
+    SshHostKeyCheckingAllowNoMatch,
+
+    /// Continue connection if the key doesn't match the stored key for the host
+    SshHostKeyCheckingAllowMismatch
+};
+
+/*!
+ * \brief Class to use to specify parameters used during connection.
+ */
+class QSSH_EXPORT SshConnectionParameters
+{
+public:
+
+    /*!
+     * \brief What kinds of authentication to attempt
+     */
+    enum AuthenticationType {
+        AuthenticationTypePassword, ///< Only attempt to connect using the password set with setPassword().
+        AuthenticationTypePublicKey, ///< Only attempt to authenticate with public key
+
+        /// Only attempt keyboard interactive authentication.
+        /// For now this only changes what to send to the server,
+        /// we will still just try to use the password set here.
+        AuthenticationTypeKeyboardInteractive,
+
+        /// Any method using the password set with setPassword().
+        /// Some servers disable \a "password", others disable \a "keyboard-interactive"
+        AuthenticationTypeTryAllPasswordBasedMethods,
+
+        /// ssh-agent authentication only
+        AuthenticationTypeAgent,
+    };
+
+    SshConnectionParameters();
+
+    /*!
+     * \brief Returns the hostname or IP set with setHost()
+     */
+    QString host() const { return url.host(); }
+
+    /*!
+     * \brief Returns the port set with setPort()
+     */
+    int port() const { return url.port(); }
+
+    /*!
+     * \brief Returns the username set with setUsername()
+     * \return
+     */
+    QString userName() const { return url.userName(); }
+
+    /*!
+     * \brief Returns the password set with setPassword()
+     */
+    QString password() const { return url.password(); }
+
+    /*!
+     * \brief Sets the hostname or IP to connect to
+     * \param host The remote host
+     */
+    void setHost(const QString &host) { url.setHost(host); }
+
+    /*!
+     * \brief Sets the remote port to use
+     * \param port
+     */
+    void setPort(int port) { url.setPort(port); }
+
+    /*!
+     * \brief Sets the username to use
+     * \param name Username
+     */
+    void setUserName(const QString &name) { url.setUserName(name); }
+
+    /*!
+     * \brief Sets the password to attempt to use
+     * \param password
+     */
+    void setPassword(const QString &password) { url.setPassword(password); }
+
+    QUrl url;
+    QString privateKeyFile;
+    int timeout; // In seconds.
+    AuthenticationType authenticationType;
+    SshConnectionOptions options;
+    SshHostKeyCheckingMode hostKeyCheckingMode;
+    SshHostKeyDatabasePtr hostKeyDatabase;
+};
+
+/// @cond
+QSSH_EXPORT bool operator==(const SshConnectionParameters &p1, const SshConnectionParameters &p2);
+QSSH_EXPORT bool operator!=(const SshConnectionParameters &p1, const SshConnectionParameters &p2);
+/// @endcond
+
+/*!
+ * \brief Network connection info.
+ */
+class QSSH_EXPORT SshConnectionInfo
+{
+public:
+    SshConnectionInfo() : localPort(0), peerPort(0) {}
+    SshConnectionInfo(const QHostAddress &la, quint16 lp, const QHostAddress &pa, quint16 pp)
+        : localAddress(la), localPort(lp), peerAddress(pa), peerPort(pp) {}
+
+    QHostAddress localAddress;
+    quint16 localPort;
+    QHostAddress peerAddress;
+    quint16 peerPort;
+};
+
+
+/*!
+    \class QSsh::SshConnection
+
+    \brief This class provides an SSH connection, implementing protocol version 2.0
+
+    See acquireConnection() which provides a pool mechanism for re-use.
+
+    It can spawn channels for remote execution and SFTP operations (version 3).
+    It operates asynchronously (non-blocking) and is not thread-safe.
+*/
+
+class QSSH_EXPORT SshConnection : public QObject
+{
+    Q_OBJECT
+
+public:
+    /*!
+     * \brief The current state of a connection
+     */
+    enum State { Unconnected, Connecting, Connected };
+
+    /*!
+     * \param serverInfo serverInfo connection parameters
+     * \param parent Parent object.
+     */
+    explicit SshConnection(const SshConnectionParameters &serverInfo, QObject *parent = nullptr);
+
+    void connectToHost();
+    void disconnectFromHost();
+
+    /*!
+     * \brief Current state of this connection
+     */
+    State state() const;
+
+    /*!
+     * \brief Returns the error state of the connection
+     * \returns If there is no error, returns \ref SshNoError if the connection is OK
+     */
+    SshError errorState() const;
+    QString errorString() const;
+    SshConnectionParameters connectionParameters() const;
+    SshConnectionInfo connectionInfo() const;
+    ~SshConnection();
+
+    /*!
+     * \brief Use this to launch remote commands
+     * \param command The command to execute
+     */
+    QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
+
+    /*!
+     * \brief Creates a remote interactive session with a shell
+     */
+    QSharedPointer<SshRemoteProcess> createRemoteShell();
+    QSharedPointer<SftpChannel> createSftpChannel();
+    QSharedPointer<SshDirectTcpIpTunnel> createDirectTunnel(const QString &originatingHost,
+            quint16 originatingPort, const QString &remoteHost, quint16 remotePort);
+    QSharedPointer<SshTcpIpForwardServer> createForwardServer(const QString &remoteHost,
+            quint16 remotePort);
+
+    // -1 if an error occurred, number of channels closed otherwise.
+    int closeAllChannels();
+    int channelCount() const;
+    const QByteArray &hostKeyFingerprint() const;
+
+    /*!
+     * \brief The X11 display name used for X11 forwarding
+     * \return The name of the X11 display set for this connection
+     */
+    QString x11DisplayName() const;
+
+signals:
+    /*!
+     * \brief Emitted when ready for use
+     */
+    void connected();
+
+    /*!
+     * \brief Emitted when the connection has been closed
+     */
+    void disconnected();
+
+    /*!
+     * \brief Emitted when data has been received
+     * \param message The content of the data, same as the output you would get when running \a ssh on the command line
+     */
+    void dataAvailable(const QString &message);
+
+    /*!
+     * \brief Emitted when an error occured
+     */
+    void error(QSsh::SshError);
+
+private:
+    Internal::SshConnectionPrivate *d;
+};
+
+} // namespace QSsh
+
+Q_DECLARE_METATYPE(QSsh::SshConnectionParameters::AuthenticationType)
+
+#endif // SSHCONNECTION_H

+ 204 - 0
src/tool/serverctrl/qssh/sshconnection_p.h

@@ -0,0 +1,204 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHCONNECTION_P_H
+#define SSHCONNECTION_P_H
+
+#include "sshconnection.h"
+#include "sshexception_p.h"
+#include "sshincomingpacket_p.h"
+#include "sshsendfacility_p.h"
+
+#include <QHash>
+#include <QList>
+#include <QQueue>
+#include <QObject>
+#include <QPair>
+#include <QScopedPointer>
+#include <QTimer>
+
+QT_BEGIN_NAMESPACE
+class QTcpSocket;
+QT_END_NAMESPACE
+
+namespace QSsh {
+class SftpChannel;
+class SshRemoteProcess;
+class SshDirectTcpIpTunnel;
+class SshTcpIpForwardServer;
+
+namespace Internal {
+class SshChannelManager;
+
+// NOTE: When you add stuff here, don't forget to update m_packetHandlers.
+enum SshStateInternal {
+    SocketUnconnected, // initial and after disconnect
+    SocketConnecting, // After connectToHost()
+    SocketConnected, // After socket's connected() signal
+    UserAuthServiceRequested,
+    WaitingForAgentKeys,
+    UserAuthRequested,
+    ConnectionEstablished // After service has been started
+    // ...
+};
+
+enum SshKeyExchangeState {
+    NoKeyExchange,
+    KexInitSent,
+    DhInitSent,
+    NewKeysSent,
+    KeyExchangeSuccess  // After server's DH_REPLY message
+};
+
+class SshConnectionPrivate : public QObject
+{
+    Q_OBJECT
+    friend class QSsh::SshConnection;
+public:
+    SshConnectionPrivate(SshConnection *conn,
+        const SshConnectionParameters &serverInfo);
+    ~SshConnectionPrivate();
+
+    void connectToHost();
+    void closeConnection(SshErrorCode sshError, SshError userError,
+        const QByteArray &serverErrorString, const QString &userErrorString);
+    QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
+    QSharedPointer<SshRemoteProcess> createRemoteShell();
+    QSharedPointer<SftpChannel> createSftpChannel();
+    QSharedPointer<SshDirectTcpIpTunnel> createDirectTunnel(const QString &originatingHost,
+            quint16 originatingPort, const QString &remoteHost, quint16 remotePort);
+    QSharedPointer<SshTcpIpForwardServer> createForwardServer(const QString &remoteHost,
+            quint16 remotePort);
+
+    SshStateInternal state() const { return m_state; }
+    SshError errorState() const { return m_error; }
+    QString errorString() const { return m_errorString; }
+    const QByteArray &hostKeyFingerprint() const { return m_hostFingerprint; }
+
+signals:
+    void connected();
+    void disconnected();
+    void dataAvailable(const QString &message);
+    void error(QSsh::SshError);
+
+private:
+    void handleSocketConnected();
+    void handleIncomingData();
+    void handleSocketError();
+    void handleSocketDisconnected();
+    void handleTimeout();
+    void sendKeepAlivePacket();
+
+    void handleAgentKeysUpdated();
+    void handleSignatureFromAgent(const QByteArray &key, const QByteArray &signature, uint token);
+    void tryAllAgentKeys();
+    void authenticateWithPublicKey();
+    void setAgentError();
+
+    void handleServerId();
+    void handlePackets();
+    void handleCurrentPacket();
+    void handleKeyExchangeInitPacket();
+    void handleKeyExchangeReplyPacket();
+    void handleNewKeysPacket();
+    void handleServiceAcceptPacket();
+    void handlePasswordExpiredPacket();
+    void handleUserAuthInfoRequestPacket();
+    void handleUserAuthSuccessPacket();
+    void handleUserAuthFailurePacket();
+    void handleUserAuthKeyOkPacket();
+    void handleUserAuthBannerPacket();
+    void handleUnexpectedPacket();
+    void handleGlobalRequest();
+    void handleDebugPacket();
+    void handleUnimplementedPacket();
+    void handleChannelRequest();
+    void handleChannelOpen();
+    void handleChannelOpenFailure();
+    void handleChannelOpenConfirmation();
+    void handleChannelSuccess();
+    void handleChannelFailure();
+    void handleChannelWindowAdjust();
+    void handleChannelData();
+    void handleChannelExtendedData();
+    void handleChannelEof();
+    void handleChannelClose();
+    void handleDisconnect();
+    void handleRequestSuccess();
+    void handleRequestFailure();
+
+    bool canUseSocket() const;
+    void createPrivateKey();
+
+    void sendData(const QByteArray &data);
+
+    uint tokenForAgent() const;
+
+    typedef void (SshConnectionPrivate::*PacketHandler)();
+    typedef QList<SshStateInternal> StateList;
+    void setupPacketHandlers();
+    void setupPacketHandler(SshPacketType type, const StateList &states,
+        PacketHandler handler);
+
+    typedef QPair<StateList, PacketHandler> HandlerInStates;
+    QHash<SshPacketType, HandlerInStates> m_packetHandlers;
+
+    static const quint64 InvalidSeqNr;
+
+    QTcpSocket *m_socket;
+    SshStateInternal m_state;
+    SshKeyExchangeState m_keyExchangeState;
+    SshIncomingPacket m_incomingPacket;
+    SshSendFacility m_sendFacility;
+    SshChannelManager * const m_channelManager;
+    const SshConnectionParameters m_connParams;
+    QByteArray m_incomingData;
+    SshError m_error;
+    QString m_errorString;
+    QScopedPointer<SshKeyExchange> m_keyExchange;
+    QByteArray m_hostFingerprint;
+    QTimer m_timeoutTimer;
+    QTimer m_keepAliveTimer;
+    bool m_ignoreNextPacket;
+    SshConnection *m_conn;
+    quint64 m_lastInvalidMsgSeqNr;
+    QByteArray m_serverId;
+    QByteArray m_agentSignature;
+    QQueue<QByteArray> m_pendingKeyChecks;
+    QByteArray m_agentKeyToUse;
+    bool m_serverHasSentDataBeforeId;
+    bool m_triedAllPasswordBasedMethods;
+    bool m_agentKeysUpToDate;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHCONNECTION_P_H

+ 276 - 0
src/tool/serverctrl/qssh/sshconnectionmanager.cpp

@@ -0,0 +1,276 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshconnectionmanager.h"
+
+#include "sshconnection.h"
+
+#include <QCoreApplication>
+#include <QList>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QObject>
+#include <QThread>
+#include <QTimer>
+
+namespace QSsh {
+namespace Internal {
+class UnaquiredConnection {
+public:
+    UnaquiredConnection(SshConnection *conn) : connection(conn), scheduledForRemoval(false) {}
+
+    SshConnection *connection;
+    bool scheduledForRemoval;
+};
+bool operator==(UnaquiredConnection c1, UnaquiredConnection c2) {
+    return c1.connection == c2.connection;
+}
+bool operator!=(UnaquiredConnection c1, UnaquiredConnection c2) {
+    return !(c1 == c2);
+}
+
+class SshConnectionManager : public QObject
+{
+    Q_OBJECT
+
+public:
+    SshConnectionManager()
+    {
+        moveToThread(QCoreApplication::instance()->thread());
+        connect(&m_removalTimer, &QTimer::timeout,
+                this, &SshConnectionManager::removeInactiveConnections);
+        m_removalTimer.setTimerType(Qt::VeryCoarseTimer);
+        m_removalTimer.start(150000); // For a total timeout of five minutes.
+    }
+
+    ~SshConnectionManager()
+    {
+        for (const UnaquiredConnection &connection : m_unacquiredConnections) {
+            disconnect(connection.connection, nullptr, this, nullptr);
+            delete connection.connection;
+        }
+
+        QSSH_ASSERT(m_acquiredConnections.isEmpty());
+        QSSH_ASSERT(m_deprecatedConnections.isEmpty());
+    }
+
+    SshConnection *acquireConnection(const SshConnectionParameters &sshParams)
+    {
+        QMutexLocker locker(&m_listMutex);
+
+        // Check in-use connections:
+        for (SshConnection * const connection : m_acquiredConnections) {
+            if (connection->connectionParameters() != sshParams)
+                continue;
+
+            if (connection->thread() != QThread::currentThread())
+                continue;
+
+            if (m_deprecatedConnections.contains(connection)) // we were asked to no longer use this one...
+                continue;
+
+            m_acquiredConnections.append(connection);
+            return connection;
+        }
+
+        // Check cached open connections:
+        for (const UnaquiredConnection &c : m_unacquiredConnections) {
+            SshConnection * const connection = c.connection;
+            if (connection->state() != SshConnection::Connected
+                    || connection->connectionParameters() != sshParams)
+                continue;
+
+            if (connection->thread() != QThread::currentThread()) {
+                if (connection->channelCount() != 0)
+                    continue;
+                QMetaObject::invokeMethod(this, "switchToCallerThread",
+                    Qt::BlockingQueuedConnection,
+                    Q_ARG(SshConnection *, connection),
+                    Q_ARG(QObject *, QThread::currentThread()));
+            }
+
+            m_unacquiredConnections.removeOne(c);
+            m_acquiredConnections.append(connection);
+            return connection;
+        }
+
+        // create a new connection:
+        SshConnection * const connection = new SshConnection(sshParams);
+        connect(connection, &SshConnection::disconnected,
+                this, &SshConnectionManager::cleanup);
+        m_acquiredConnections.append(connection);
+
+        return connection;
+    }
+
+    void releaseConnection(SshConnection *connection)
+    {
+        QMutexLocker locker(&m_listMutex);
+
+        const bool wasAquired = m_acquiredConnections.removeOne(connection);
+        QSSH_ASSERT_AND_RETURN(wasAquired);
+        if (m_acquiredConnections.contains(connection))
+            return;
+
+        bool doDelete = false;
+        connection->moveToThread(QCoreApplication::instance()->thread());
+        if (m_deprecatedConnections.removeOne(connection)
+                || connection->state() != SshConnection::Connected) {
+            doDelete = true;
+        } else {
+            QSSH_ASSERT_AND_RETURN(!m_unacquiredConnections.contains(UnaquiredConnection(connection)));
+
+            // It can happen that two or more connections with the same parameters were acquired
+            // if the clients were running in different threads. Only keep one of them in
+            // such a case.
+            bool haveConnection = false;
+            for (const UnaquiredConnection &c : m_unacquiredConnections) {
+                if (c.connection->connectionParameters() == connection->connectionParameters()) {
+                    haveConnection = true;
+                    break;
+                }
+            }
+            if (!haveConnection) {
+                connection->closeAllChannels(); // Clean up after neglectful clients.
+                m_unacquiredConnections.append(UnaquiredConnection(connection));
+            } else {
+                doDelete = true;
+            }
+        }
+
+        if (doDelete) {
+            disconnect(connection, nullptr, this, nullptr);
+            m_deprecatedConnections.removeAll(connection);
+            connection->deleteLater();
+        }
+    }
+
+    void forceNewConnection(const SshConnectionParameters &sshParams)
+    {
+        QMutexLocker locker(&m_listMutex);
+
+        for (int i = 0; i < m_unacquiredConnections.count(); ++i) {
+            SshConnection * const connection = m_unacquiredConnections.at(i).connection;
+            if (connection->connectionParameters() == sshParams) {
+                disconnect(connection, nullptr, this, nullptr);
+                delete connection;
+                m_unacquiredConnections.removeAt(i);
+                break;
+            }
+        }
+
+        for (SshConnection * const connection : m_acquiredConnections) {
+            if (connection->connectionParameters() == sshParams) {
+                if (!m_deprecatedConnections.contains(connection))
+                    m_deprecatedConnections.append(connection);
+            }
+        }
+    }
+
+private:
+    Q_INVOKABLE void switchToCallerThread(SshConnection *connection, QObject *threadObj)
+    {
+        connection->moveToThread(qobject_cast<QThread *>(threadObj));
+    }
+
+    void cleanup()
+    {
+        QMutexLocker locker(&m_listMutex);
+
+        SshConnection *currentConnection = qobject_cast<SshConnection *>(sender());
+        if (!currentConnection)
+            return;
+
+        if (m_unacquiredConnections.removeOne(UnaquiredConnection(currentConnection))) {
+            disconnect(currentConnection, nullptr, this, nullptr);
+            currentConnection->deleteLater();
+        }
+    }
+
+    void removeInactiveConnections()
+    {
+        QMutexLocker locker(&m_listMutex);
+        for (int i = m_unacquiredConnections.count() - 1; i >= 0; --i) {
+            UnaquiredConnection &c = m_unacquiredConnections[i];
+            if (c.scheduledForRemoval) {
+                disconnect(c.connection, nullptr, this, nullptr);
+                c.connection->deleteLater();
+                m_unacquiredConnections.removeAt(i);
+            } else {
+                c.scheduledForRemoval = true;
+            }
+        }
+    }
+
+private:
+    // We expect the number of concurrently open connections to be small.
+    // If that turns out to not be the case, we can still use a data
+    // structure with faster access.
+    QList<UnaquiredConnection> m_unacquiredConnections;
+
+    // Can contain the same connection more than once; this acts as a reference count.
+    QList<SshConnection *> m_acquiredConnections;
+
+    QList<SshConnection *> m_deprecatedConnections;
+    QMutex m_listMutex;
+    QTimer m_removalTimer;
+};
+
+} // namespace Internal
+
+static QMutex instanceMutex;
+
+static Internal::SshConnectionManager &instance()
+{
+    static Internal::SshConnectionManager manager;
+    return manager;
+}
+
+SshConnection *acquireConnection(const SshConnectionParameters &sshParams)
+{
+    QMutexLocker locker(&instanceMutex);
+    return instance().acquireConnection(sshParams);
+}
+
+void releaseConnection(SshConnection *connection)
+{
+    QMutexLocker locker(&instanceMutex);
+    instance().releaseConnection(connection);
+}
+
+void forceNewConnection(const SshConnectionParameters &sshParams)
+{
+    QMutexLocker locker(&instanceMutex);
+    instance().forceNewConnection(sshParams);
+}
+
+} // namespace QSsh
+
+#include "sshconnectionmanager.moc"

+ 63 - 0
src/tool/serverctrl/qssh/sshconnectionmanager.h

@@ -0,0 +1,63 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHCONNECTIONMANAGER_H
+#define SSHCONNECTIONMANAGER_H
+
+#include "ssh_global.h"
+
+namespace QSsh {
+
+class SshConnection;
+class SshConnectionParameters;
+
+/*!
+ * \brief Creates a new connection or returns an existing one if there already is one with identical sshParams
+ * \param sshParams Parameters used during connection
+ * \return A connection
+ */
+QSSH_EXPORT SshConnection *acquireConnection(const SshConnectionParameters &sshParams);
+
+/*!
+ * \brief Call this when you are done with a connection, might be disconnected and destroyed if there are no others who have called acquireConnection()
+ * \param connection The connection to be released
+ */
+QSSH_EXPORT void releaseConnection(SshConnection *connection);
+
+/*!
+ * \brief Creates a new connection, unlike acquireConnection() it will not reuse an existing one.
+ * \param sshParams Parameters used during connection
+ * Make sure the next acquireConnection with the given parameters will return a new connection.
+ */
+QSSH_EXPORT void forceNewConnection(const SshConnectionParameters &sshParams);
+
+} // namespace QSsh
+
+#endif // SSHCONNECTIONMANAGER_H

+ 465 - 0
src/tool/serverctrl/qssh/sshcryptofacility.cpp

@@ -0,0 +1,465 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshcryptofacility_p.h"
+
+#include "opensshkeyfilereader_p.h"
+#include "sshbotanconversions_p.h"
+#include "sshcapabilities_p.h"
+#include "sshexception_p.h"
+#include "sshkeyexchange_p.h"
+#include "sshkeypasswordretriever_p.h"
+#include "sshpacket_p.h"
+#include "sshlogging_p.h"
+
+#include <botan/block_cipher.h>
+#include <botan/hash.h>
+#include <botan/pkcs8.h>
+#include <botan/dsa.h>
+#include <botan/rsa.h>
+#include <botan/ber_dec.h>
+#include <botan/pubkey.h>
+#include <botan/filters.h>
+#include <botan/ecdsa.h>
+
+#include <QDebug>
+#include <QList>
+
+#include <string>
+
+using namespace Botan;
+
+namespace QSsh {
+namespace Internal {
+
+SshAbstractCryptoFacility::SshAbstractCryptoFacility()
+    : m_cipherBlockSize(0), m_macLength(0)
+{
+}
+
+SshAbstractCryptoFacility::~SshAbstractCryptoFacility() {}
+
+void SshAbstractCryptoFacility::clearKeys()
+{
+    m_cipherBlockSize = 0;
+    m_macLength = 0;
+    m_sessionId.clear();
+    m_pipe.reset(nullptr);
+    m_hMac.reset(nullptr);
+}
+
+SshAbstractCryptoFacility::Mode SshAbstractCryptoFacility::getMode(const QByteArray &algoName)
+{
+    if (algoName.endsWith("-ctr"))
+        return CtrMode;
+    if (algoName.endsWith("-cbc"))
+        return CbcMode;
+    throw SshClientException(SshInternalError, SSH_TR("Unexpected cipher \"%1\"")
+                             .arg(QString::fromLatin1(algoName)));
+}
+
+void SshAbstractCryptoFacility::recreateKeys(const SshKeyExchange &kex)
+{
+    checkInvariant();
+
+    if (m_sessionId.isEmpty())
+        m_sessionId = kex.h();
+   const QByteArray &rfcCryptAlgoName = cryptAlgoName(kex);
+
+   { // Don't know how else to get this with the new botan API
+       std::unique_ptr<BlockCipher> cipher
+               = BlockCipher::create_or_throw(botanCryptAlgoName(rfcCryptAlgoName));
+       m_cipherBlockSize = static_cast<quint32>(cipher->block_size());
+   }
+    const QByteArray ivData = generateHash(kex, ivChar(), m_cipherBlockSize);
+    const InitializationVector iv(convertByteArray(ivData), m_cipherBlockSize);
+
+    Keyed_Filter * const cipherMode
+            = makeCipherMode(botanCipherAlgoName(rfcCryptAlgoName), getMode(rfcCryptAlgoName));
+
+    const quint32 keySize = static_cast<quint32>(cipherMode->key_spec().maximum_keylength());
+    const QByteArray cryptKeyData = generateHash(kex, keyChar(), keySize);
+    SymmetricKey cryptKey(convertByteArray(cryptKeyData), keySize);
+
+    cipherMode->set_key(cryptKey);
+    cipherMode->set_iv(iv);
+
+    m_pipe.reset(new Pipe(cipherMode));
+
+    m_macLength = botanHMacKeyLen(hMacAlgoName(kex));
+    const QByteArray hMacKeyData = generateHash(kex, macChar(), macLength());
+    SymmetricKey hMacKey(convertByteArray(hMacKeyData), macLength());
+    m_hMac = MessageAuthenticationCode::create_or_throw("HMAC(" + std::string(botanHMacAlgoName(hMacAlgoName(kex))) + ")");
+    m_hMac->set_key(hMacKey);
+}
+
+void SshAbstractCryptoFacility::convert(QByteArray &data, quint32 offset,
+    quint32 dataSize) const
+{
+    Q_ASSERT(offset + dataSize <= static_cast<quint32>(data.size()));
+    checkInvariant();
+
+    // Session id empty => No key exchange has happened yet.
+    if (dataSize == 0 || m_sessionId.isEmpty())
+        return;
+
+    if (dataSize % cipherBlockSize() != 0) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid packet size");
+    }
+    m_pipe->process_msg(reinterpret_cast<const byte *>(data.constData()) + offset,
+        dataSize);
+     // Can't use Pipe::LAST_MESSAGE because of a VC bug.
+    quint32 bytesRead = static_cast<quint32>(m_pipe->read(
+          reinterpret_cast<byte *>(data.data()) + offset, dataSize, m_pipe->message_count() - 1));
+    if (bytesRead != dataSize) {
+        throw SshClientException(SshInternalError,
+                QLatin1String("Internal error: Botan::Pipe::read() returned unexpected value"));
+    }
+}
+
+Keyed_Filter *SshAbstractCryptoFacility::makeCtrCipherMode(const QByteArray &cipher)
+{
+    StreamCipher_Filter *filter = new StreamCipher_Filter(cipher.toStdString());
+    return filter;
+}
+
+QByteArray SshAbstractCryptoFacility::generateMac(const QByteArray &data,
+    quint32 dataSize) const
+{
+    return m_sessionId.isEmpty()
+        ? QByteArray()
+        : convertByteArray(m_hMac->process(reinterpret_cast<const byte *>(data.constData()),
+              dataSize));
+}
+
+QByteArray SshAbstractCryptoFacility::generateHash(const SshKeyExchange &kex,
+    char c, quint32 length)
+{
+    const QByteArray &k = kex.k();
+    const QByteArray &h = kex.h();
+    QByteArray data(k);
+    data.append(h).append(c).append(m_sessionId);
+    SecureVector<byte> key
+        = kex.hash()->process(convertByteArray(data), data.size());
+    while (key.size() < length) {
+        secure_vector<byte> tmpKey;
+        tmpKey += secure_vector<byte>(k.begin(), k.end());
+        tmpKey += secure_vector<byte>(h.begin(), h.end());
+        tmpKey += key;
+        key += kex.hash()->process(tmpKey);
+    }
+    return QByteArray(reinterpret_cast<const char *>(key.data()), length);
+}
+
+void SshAbstractCryptoFacility::checkInvariant() const
+{
+    Q_ASSERT(m_sessionId.isEmpty() == !m_pipe);
+}
+
+
+const QByteArray SshEncryptionFacility::PrivKeyFileStartLineRsa("-----BEGIN RSA PRIVATE KEY-----");
+const QByteArray SshEncryptionFacility::PrivKeyFileStartLineDsa("-----BEGIN DSA PRIVATE KEY-----");
+const QByteArray SshEncryptionFacility::PrivKeyFileEndLineRsa("-----END RSA PRIVATE KEY-----");
+const QByteArray SshEncryptionFacility::PrivKeyFileEndLineDsa("-----END DSA PRIVATE KEY-----");
+const QByteArray SshEncryptionFacility::PrivKeyFileStartLineEcdsa("-----BEGIN EC PRIVATE KEY-----");
+const QByteArray SshEncryptionFacility::PrivKeyFileEndLineEcdsa("-----END EC PRIVATE KEY-----");
+
+QByteArray SshEncryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
+{
+    return kex.encryptionAlgo();
+}
+
+QByteArray SshEncryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
+{
+    return kex.hMacAlgoClientToServer();
+}
+
+Keyed_Filter *SshEncryptionFacility::makeCipherMode(const QByteArray &cipher, const Mode mode)
+{
+    if (mode == CtrMode) {
+        return new StreamCipher_Filter(cipher.toStdString());
+    }
+
+    qWarning() << "I haven't been able to test the CBC encryption modes, so if this files file a bug at https://github.com/sandsmark/QSsh";
+
+    Cipher_Mode_Filter *filter = new Cipher_Mode_Filter(
+                Cipher_Mode::create_or_throw(cipher.toStdString(), ENCRYPTION).release()); // We have to release, otherwise clang fails to link
+    return filter;
+}
+
+void SshEncryptionFacility::encrypt(QByteArray &data) const
+{
+    convert(data, 0, data.size());
+}
+
+void SshEncryptionFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
+{
+    if (privKeyFileContents == m_cachedPrivKeyContents)
+        return;
+
+    m_authKeyAlgoName.clear();
+    qCDebug(sshLog, "%s: Key not cached, reading", Q_FUNC_INFO);
+    QList<BigInt> pubKeyParams;
+    QList<BigInt> allKeyParams;
+    QString error1;
+    QString error2;
+    OpenSshKeyFileReader openSshReader(m_rng);
+    if (openSshReader.parseKey(privKeyFileContents)) {
+        m_authKeyAlgoName = openSshReader.keyType();
+        m_authKey.reset(openSshReader.privateKey().release());
+        pubKeyParams = openSshReader.publicParameters();
+        allKeyParams = openSshReader.allParameters();
+    } else if (!createAuthenticationKeyFromPKCS8(privKeyFileContents, pubKeyParams, allKeyParams,
+                                                 error1)
+            && !createAuthenticationKeyFromOpenSSL(privKeyFileContents, pubKeyParams, allKeyParams,
+                error2)) {
+        qCDebug(sshLog, "%s: %s\n\t%s\n", Q_FUNC_INFO, qPrintable(error1), qPrintable(error2));
+        throw SshClientException(SshKeyFileError, SSH_TR("Decoding of private key file failed: "
+            "Format not understood."));
+    }
+
+    for (const BigInt &b : allKeyParams) {
+        if (b.is_zero()) {
+            throw SshClientException(SshKeyFileError,
+                SSH_TR("Decoding of private key file failed: Invalid zero parameter."));
+        }
+    }
+
+    m_authPubKeyBlob = AbstractSshPacket::encodeString(m_authKeyAlgoName);
+    auto * const ecdsaKey = dynamic_cast<ECDSA_PrivateKey *>(m_authKey.data());
+    if (ecdsaKey) {
+        m_authPubKeyBlob += AbstractSshPacket::encodeString(m_authKeyAlgoName.mid(11)); // Without "ecdsa-sha2-" prefix.
+        m_authPubKeyBlob += AbstractSshPacket::encodeString(
+                    convertByteArray(ecdsaKey->public_point().encode(PointGFp::UNCOMPRESSED)));
+    } else {
+        for (const BigInt &b : pubKeyParams) {
+            m_authPubKeyBlob += AbstractSshPacket::encodeMpInt(b);
+        }
+    }
+    m_cachedPrivKeyContents = privKeyFileContents;
+}
+
+bool SshEncryptionFacility::createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
+    QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
+{
+    try {
+        Pipe pipe;
+        pipe.process_msg(convertByteArray(privKeyFileContents), privKeyFileContents.size());
+        m_authKey.reset(PKCS8::load_key(pipe, m_rng, SshKeyPasswordRetriever::get_passphrase));
+        if (auto * const dsaKey = dynamic_cast<DSA_PrivateKey *>(m_authKey.data())) {
+            m_authKeyAlgoName = SshCapabilities::PubKeyDss;
+            pubKeyParams << dsaKey->group_p() << dsaKey->group_q()
+                         << dsaKey->group_g() << dsaKey->get_y();
+            allKeyParams << pubKeyParams << dsaKey->get_x();
+        } else if (auto * const rsaKey = dynamic_cast<RSA_PrivateKey *>(m_authKey.data())) {
+            m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
+            pubKeyParams << rsaKey->get_e() << rsaKey->get_n();
+            allKeyParams << pubKeyParams << rsaKey->get_p() << rsaKey->get_q()
+                         << rsaKey->get_d();
+        } else if (auto * const ecdsaKey = dynamic_cast<ECDSA_PrivateKey *>(m_authKey.data())) {
+            const BigInt value = ecdsaKey->private_value();
+            m_authKeyAlgoName = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(
+                        static_cast<int>(value.bytes()));
+            pubKeyParams << ecdsaKey->public_point().get_affine_x()
+                         << ecdsaKey->public_point().get_affine_y();
+            allKeyParams << pubKeyParams << value;
+        } else {
+            qCWarning(sshLog, "%s: Unexpected code flow, expected success or exception.",
+                      Q_FUNC_INFO);
+            return false;
+        }
+    } catch (const std::exception &ex) {
+        error = QLatin1String(ex.what());
+        return false;
+    }
+
+    return true;
+}
+
+bool SshEncryptionFacility::createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
+    QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
+{
+    try {
+        bool syntaxOk = true;
+        QList<QByteArray> lines = privKeyFileContents.split('\n');
+        while (lines.last().isEmpty())
+            lines.removeLast();
+        if (lines.count() < 3) {
+            syntaxOk = false;
+        } else if (lines.first() == PrivKeyFileStartLineRsa) {
+            if (lines.last() != PrivKeyFileEndLineRsa)
+                syntaxOk = false;
+            else
+                m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
+        } else if (lines.first() == PrivKeyFileStartLineDsa) {
+            if (lines.last() != PrivKeyFileEndLineDsa)
+                syntaxOk = false;
+            else
+                m_authKeyAlgoName = SshCapabilities::PubKeyDss;
+        } else if (lines.first() == PrivKeyFileStartLineEcdsa) {
+            if (lines.last() != PrivKeyFileEndLineEcdsa)
+                syntaxOk = false;
+            // m_authKeyAlgoName set below, as we don't know the size yet.
+        } else {
+            syntaxOk = false;
+        }
+        if (!syntaxOk) {
+            error = SSH_TR("Unexpected format.");
+            return false;
+        }
+
+        QByteArray privateKeyBlob;
+        for (int i = 1; i < lines.size() - 1; ++i)
+            privateKeyBlob += lines.at(i);
+        privateKeyBlob = QByteArray::fromBase64(privateKeyBlob);
+
+        BER_Decoder decoder(convertByteArray(privateKeyBlob), privateKeyBlob.size());
+        BER_Decoder sequence = decoder.start_cons(SEQUENCE);
+        size_t version;
+        sequence.decode (version);
+        const size_t expectedVersion = m_authKeyAlgoName.isEmpty() ? 1 : 0;
+        if (version != expectedVersion) {
+            error = SSH_TR("Key encoding has version %1, expected %2.")
+                    .arg(version).arg(expectedVersion);
+            return false;
+        }
+
+        if (m_authKeyAlgoName == SshCapabilities::PubKeyDss) {
+            BigInt p, q, g, y, x;
+            sequence.decode (p).decode (q).decode (g).decode (y).decode (x);
+            DSA_PrivateKey * const dsaKey = new DSA_PrivateKey(m_rng, DL_Group(p, q, g), x);
+            m_authKey.reset(dsaKey);
+            pubKeyParams << p << q << g << y;
+            allKeyParams << pubKeyParams << x;
+        } else if (m_authKeyAlgoName == SshCapabilities::PubKeyRsa) {
+            BigInt p, q, e, d, n;
+            sequence.decode(n).decode(e).decode(d).decode(p).decode(q);
+            RSA_PrivateKey * const rsaKey = new RSA_PrivateKey(p, q, e, d, n);
+            m_authKey.reset(rsaKey);
+            pubKeyParams << e << n;
+            allKeyParams << pubKeyParams << p << q << d;
+        } else {
+            BigInt privKey;
+            sequence.decode_octet_string_bigint(privKey);
+            m_authKeyAlgoName = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(
+                        static_cast<int>(privKey.bytes()));
+            const EC_Group group(SshCapabilities::oid(m_authKeyAlgoName));
+            auto * const key = new ECDSA_PrivateKey(m_rng, group, privKey);
+            m_authKey.reset(key);
+            pubKeyParams << key->public_point().get_affine_x()
+                         << key->public_point().get_affine_y();
+            allKeyParams << pubKeyParams << privKey;
+        }
+
+        sequence.discard_remaining();
+        sequence.verify_end();
+    } catch (const std::exception &ex) {
+        error = QLatin1String(ex.what());
+        return false;
+    }
+    return true;
+}
+
+QByteArray SshEncryptionFacility::authenticationAlgorithmName() const
+{
+    Q_ASSERT(m_authKey);
+    return m_authKeyAlgoName;
+}
+
+QByteArray SshEncryptionFacility::authenticationKeySignature(const QByteArray &data) const
+{
+    Q_ASSERT(m_authKey);
+
+    QScopedPointer<PK_Signer> signer(new PK_Signer(*m_authKey,
+        m_rng,
+        botanEmsaAlgoName(m_authKeyAlgoName)));
+    QByteArray dataToSign = AbstractSshPacket::encodeString(sessionId()) + data;
+    QByteArray signature
+        = convertByteArray(signer->sign_message(convertByteArray(dataToSign),
+              dataToSign.size(), m_rng));
+    if (m_authKeyAlgoName.startsWith(SshCapabilities::PubKeyEcdsaPrefix)) {
+        // The Botan output is not quite in the format that SSH defines.
+        const int halfSize = signature.count() / 2;
+        const BigInt r = BigInt::decode(convertByteArray(signature), halfSize);
+        const BigInt s = BigInt::decode(convertByteArray(signature.mid(halfSize)), halfSize);
+        signature = AbstractSshPacket::encodeMpInt(r) + AbstractSshPacket::encodeMpInt(s);
+    }
+    return AbstractSshPacket::encodeString(m_authKeyAlgoName)
+        + AbstractSshPacket::encodeString(signature);
+}
+
+QByteArray SshEncryptionFacility::getRandomNumbers(int count) const
+{
+    QByteArray data;
+    data.resize(count);
+    m_rng.randomize(convertByteArray(data), count);
+    return data;
+}
+
+SshEncryptionFacility::~SshEncryptionFacility() {}
+
+
+QByteArray SshDecryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
+{
+    return kex.decryptionAlgo();
+}
+
+QByteArray SshDecryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
+{
+    return kex.hMacAlgoServerToClient();
+}
+
+Keyed_Filter *SshDecryptionFacility::makeCipherMode(const QByteArray &cipher, const Mode mode)
+{
+    if (mode == CtrMode) {
+        return new StreamCipher_Filter(cipher.toStdString());
+    }
+
+    qWarning() << "I haven't been able to test the CBC decryption modes, so if this files file a bug at https://github.com/sandsmark/QSsh";
+
+    Cipher_Mode_Filter *filter = new Cipher_Mode_Filter(
+                Cipher_Mode::create_or_throw(cipher.toStdString(), DECRYPTION).release()); // We have to release, otherwise clang fails to link
+    return filter;
+}
+
+void SshDecryptionFacility::decrypt(QByteArray &data, quint32 offset,
+    quint32 dataSize) const
+{
+    convert(data, offset, dataSize);
+    qCDebug(sshLog, "Decrypted data:");
+    const char * const start = data.constData() + offset;
+    const char * const end = start + dataSize;
+    for (const char *c = start; c < end; ++c)
+        qCDebug(sshLog) << "'" << *c << "' (0x" << (static_cast<int>(*c) & 0xff) << ")";
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 149 - 0
src/tool/serverctrl/qssh/sshcryptofacility_p.h

@@ -0,0 +1,149 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHABSTRACTCRYPTOFACILITY_P_H
+#define SSHABSTRACTCRYPTOFACILITY_P_H
+
+#include <botan/filters.h>
+#include <botan/block_cipher.h>
+#include <botan/pipe.h>
+#include <botan/bigint.h>
+#include <botan/pk_keys.h>
+#include <botan/auto_rng.h>
+
+#include <QByteArray>
+#include <QScopedPointer>
+
+namespace QSsh {
+namespace Internal {
+
+class SshKeyExchange;
+
+class SshAbstractCryptoFacility
+{
+public:
+    virtual ~SshAbstractCryptoFacility();
+
+    void clearKeys();
+    void recreateKeys(const SshKeyExchange &kex);
+    QByteArray generateMac(const QByteArray &data, quint32 dataSize) const;
+    quint32 cipherBlockSize() const { return m_cipherBlockSize; }
+    quint32 macLength() const { return m_macLength; }
+    QByteArray sessionId() const { return m_sessionId; }
+
+    bool isValid() const { return m_hMac && m_pipe; } // TODO: probably more, but this stops segfaulting
+
+protected:
+    enum Mode { CbcMode, CtrMode };
+
+    SshAbstractCryptoFacility();
+    void convert(QByteArray &data, quint32 offset, quint32 dataSize) const;
+    Botan::Keyed_Filter *makeCtrCipherMode(const QByteArray &cipher);
+
+private:
+    SshAbstractCryptoFacility(const SshAbstractCryptoFacility &);
+    SshAbstractCryptoFacility &operator=(const SshAbstractCryptoFacility &);
+
+    virtual QByteArray cryptAlgoName(const SshKeyExchange &kex) const = 0;
+    virtual QByteArray hMacAlgoName(const SshKeyExchange &kex) const = 0;
+    virtual Botan::Keyed_Filter *makeCipherMode(const QByteArray &cipher, const Mode mode) = 0;
+    virtual char ivChar() const = 0;
+    virtual char keyChar() const = 0;
+    virtual char macChar() const = 0;
+
+    QByteArray generateHash(const SshKeyExchange &kex, char c, quint32 length);
+    void checkInvariant() const;
+    static Mode getMode(const QByteArray &algoName);
+
+    QByteArray m_sessionId;
+    std::unique_ptr<Botan::Pipe> m_pipe;
+    std::unique_ptr<Botan::MessageAuthenticationCode> m_hMac;
+    quint32 m_cipherBlockSize;
+    quint32 m_macLength;
+};
+
+class SshEncryptionFacility : public SshAbstractCryptoFacility
+{
+public:
+    void encrypt(QByteArray &data) const;
+
+    void createAuthenticationKey(const QByteArray &privKeyFileContents);
+    QByteArray authenticationAlgorithmName() const;
+    QByteArray authenticationPublicKey() const { return m_authPubKeyBlob; }
+    QByteArray authenticationKeySignature(const QByteArray &data) const;
+    QByteArray getRandomNumbers(int count) const;
+
+    ~SshEncryptionFacility();
+
+private:
+    QByteArray cryptAlgoName(const SshKeyExchange &kex) const override;
+    QByteArray hMacAlgoName(const SshKeyExchange &kex) const override;
+    Botan::Keyed_Filter *makeCipherMode(const QByteArray &cipher, const Mode mode) override;
+    char ivChar() const override { return 'A'; }
+    char keyChar() const override { return 'C'; }
+    char macChar() const override { return 'E'; }
+
+    bool createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
+        QList<Botan::BigInt> &pubKeyParams, QList<Botan::BigInt> &allKeyParams, QString &error);
+    bool createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
+        QList<Botan::BigInt> &pubKeyParams, QList<Botan::BigInt> &allKeyParams, QString &error);
+
+    static const QByteArray PrivKeyFileStartLineRsa;
+    static const QByteArray PrivKeyFileStartLineDsa;
+    static const QByteArray PrivKeyFileEndLineRsa;
+    static const QByteArray PrivKeyFileEndLineDsa;
+    static const QByteArray PrivKeyFileStartLineEcdsa;
+    static const QByteArray PrivKeyFileEndLineEcdsa;
+
+    QByteArray m_authKeyAlgoName;
+    QByteArray m_authPubKeyBlob;
+    QByteArray m_cachedPrivKeyContents;
+    QScopedPointer<Botan::Private_Key> m_authKey;
+    mutable Botan::AutoSeeded_RNG m_rng;
+};
+
+class SshDecryptionFacility : public SshAbstractCryptoFacility
+{
+public:
+    void decrypt(QByteArray &data, quint32 offset, quint32 dataSize) const;
+
+private:
+    QByteArray cryptAlgoName(const SshKeyExchange &kex) const override;
+    QByteArray hMacAlgoName(const SshKeyExchange &kex) const override;
+    Botan::Keyed_Filter *makeCipherMode(const QByteArray &cipher, const Mode mode) override;
+    char ivChar() const override { return 'B'; }
+    char keyChar() const override { return 'D'; }
+    char macChar() const override { return 'F'; }
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHABSTRACTCRYPTOFACILITY_P_H

+ 129 - 0
src/tool/serverctrl/qssh/sshdirecttcpiptunnel.cpp

@@ -0,0 +1,129 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#include "sshdirecttcpiptunnel.h"
+#include "sshdirecttcpiptunnel_p.h"
+
+#include "sshincomingpacket_p.h"
+#include "sshlogging_p.h"
+#include "sshsendfacility_p.h"
+
+#include <QTimer>
+
+namespace QSsh {
+namespace Internal {
+
+SshDirectTcpIpTunnelPrivate::SshDirectTcpIpTunnelPrivate(quint32 channelId,
+        const QString &originatingHost, quint16 originatingPort, const QString &remoteHost,
+        quint16 remotePort, SshSendFacility &sendFacility)
+    : SshTcpIpTunnelPrivate(channelId, sendFacility),
+      m_originatingHost(originatingHost),
+      m_originatingPort(originatingPort),
+      m_remoteHost(remoteHost),
+      m_remotePort(remotePort)
+{
+}
+
+void SshDirectTcpIpTunnelPrivate::handleOpenSuccessInternal()
+{
+    emit initialized();
+}
+
+} // namespace Internal
+
+using namespace Internal;
+
+SshDirectTcpIpTunnel::SshDirectTcpIpTunnel(quint32 channelId, const QString &originatingHost,
+        quint16 originatingPort, const QString &remoteHost, quint16 remotePort,
+        SshSendFacility &sendFacility)
+    : d(new SshDirectTcpIpTunnelPrivate(channelId, originatingHost, originatingPort, remoteHost,
+                                        remotePort, sendFacility))
+{
+    d->init(this);
+    connect(d, &SshDirectTcpIpTunnelPrivate::initialized,
+            this, &SshDirectTcpIpTunnel::initialized, Qt::QueuedConnection);
+}
+
+SshDirectTcpIpTunnel::~SshDirectTcpIpTunnel()
+{
+    delete d;
+}
+
+bool SshDirectTcpIpTunnel::atEnd() const
+{
+    return QIODevice::atEnd() && d->m_data.isEmpty();
+}
+
+qint64 SshDirectTcpIpTunnel::bytesAvailable() const
+{
+    return QIODevice::bytesAvailable() + d->m_data.count();
+}
+
+bool SshDirectTcpIpTunnel::canReadLine() const
+{
+    return QIODevice::canReadLine() || d->m_data.contains('\n');
+}
+
+void SshDirectTcpIpTunnel::close()
+{
+    d->closeChannel();
+    QIODevice::close();
+}
+
+void SshDirectTcpIpTunnel::initialize()
+{
+    QSSH_ASSERT_AND_RETURN(d->channelState() == AbstractSshChannel::Inactive);
+
+    try {
+        QIODevice::open(QIODevice::ReadWrite);
+        d->m_sendFacility.sendDirectTcpIpPacket(d->localChannelId(), SshDirectTcpIpTunnelPrivate::initialWindowSize(),
+            SshDirectTcpIpTunnelPrivate::maxPacketSize(), d->m_remoteHost.toUtf8(), d->m_remotePort,
+            d->m_originatingHost.toUtf8(), d->m_originatingPort);
+        d->setChannelState(AbstractSshChannel::SessionRequested);
+        d->m_timeoutTimer.setTimerType(Qt::VeryCoarseTimer);
+        d->m_timeoutTimer.start(SshDirectTcpIpTunnelPrivate::ReplyTimeout);
+    }  catch (const std::exception &e) { // Won't happen, but let's play it safe.
+        qCWarning(sshLog, "Botan error: %s", e.what());
+        d->closeChannel();
+    }
+}
+
+qint64 SshDirectTcpIpTunnel::readData(char *data, qint64 maxlen)
+{
+    return d->readData(data, maxlen);
+}
+
+qint64 SshDirectTcpIpTunnel::writeData(const char *data, qint64 len)
+{
+    return d->writeData(data, len);
+}
+
+} // namespace QSsh

+ 89 - 0
src/tool/serverctrl/qssh/sshdirecttcpiptunnel.h

@@ -0,0 +1,89 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef  SSHDIRECTTCPIPTUNNEL_H
+#define  SSHDIRECTTCPIPTUNNEL_H
+
+#include "ssh_global.h"
+
+#include <QIODevice>
+#include <QSharedPointer>
+
+namespace QSsh {
+
+namespace Internal {
+class SshChannelManager;
+class SshDirectTcpIpTunnelPrivate;
+class SshSendFacility;
+class SshTcpIpTunnelPrivate;
+} // namespace Internal
+
+class QSSH_EXPORT SshDirectTcpIpTunnel : public QIODevice
+{
+    Q_OBJECT
+
+    friend class Internal::SshChannelManager;
+    friend class Internal::SshTcpIpTunnelPrivate;
+
+public:
+    typedef QSharedPointer<SshDirectTcpIpTunnel> Ptr;
+
+    ~SshDirectTcpIpTunnel();
+
+    // QIODevice stuff
+    bool atEnd() const;
+    qint64 bytesAvailable() const;
+    bool canReadLine() const;
+    void close();
+    bool isSequential() const { return true; }
+
+    void initialize();
+
+signals:
+    void initialized();
+    void error(const QString &reason);
+
+private:
+    SshDirectTcpIpTunnel(quint32 channelId, const QString &originatingHost,
+            quint16 originatingPort, const QString &remoteHost, quint16 remotePort,
+            Internal::SshSendFacility &sendFacility);
+
+    // QIODevice stuff
+    qint64 readData(char *data, qint64 maxlen);
+    qint64 writeData(const char *data, qint64 len);
+
+    Internal::SshDirectTcpIpTunnelPrivate * const d;
+};
+
+} // namespace QSsh
+
+#endif // SSHDIRECTTCPIPTUNNEL_H

+ 68 - 0
src/tool/serverctrl/qssh/sshdirecttcpiptunnel_p.h

@@ -0,0 +1,68 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#ifndef DIRECTTCPIPCHANNEL_P_H
+#define DIRECTTCPIPCHANNEL_P_H
+
+#include "sshtcpiptunnel_p.h"
+
+namespace QSsh {
+class SshDirectTcpIpTunnel;
+
+namespace Internal {
+
+class SshDirectTcpIpTunnelPrivate : public SshTcpIpTunnelPrivate
+{
+    Q_OBJECT
+
+    friend class QSsh::SshDirectTcpIpTunnel;
+
+public:
+    explicit SshDirectTcpIpTunnelPrivate(quint32 channelId, const QString &originatingHost,
+            quint16 originatingPort, const QString &remoteHost, quint16 remotePort,
+            SshSendFacility &sendFacility);
+
+signals:
+    void initialized();
+
+private:
+    void handleOpenSuccessInternal();
+
+    const QString m_originatingHost;
+    const quint16 m_originatingPort;
+    const QString m_remoteHost;
+    const quint16 m_remotePort;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // DIRECTTCPIPCHANNEL_P_H

+ 77 - 0
src/tool/serverctrl/qssh/ssherrors.h

@@ -0,0 +1,77 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHERRORS_P_H
+#define SSHERRORS_P_H
+
+#include <QMetaType>
+
+namespace QSsh {
+
+/*!
+ * \brief SSH specific errors
+ */
+enum SshError {
+    /// No error has occured
+    SshNoError,
+
+    /// There was a network socket error
+    SshSocketError,
+
+    /// The connection timed out
+    SshTimeoutError,
+
+    /// There was an error communicating with the server
+    SshProtocolError,
+
+    /// There was a problem with the remote host key
+    SshHostKeyError,
+
+    /// We failed to read or parse the key file used for authentication
+    SshKeyFileError,
+
+    /// We failed to authenticate
+    SshAuthenticationError,
+
+    /// The server closed our connection
+    SshClosedByServerError,
+
+    /// The ssh-agent used for authenticating failed somehow
+    SshAgentError,
+
+    /// Something bad happened on the server
+    SshInternalError
+};
+
+} // namespace QSsh
+
+Q_DECLARE_METATYPE(QSsh::SshError)
+
+#endif // SSHERRORS_P_H

+ 95 - 0
src/tool/serverctrl/qssh/sshexception_p.h

@@ -0,0 +1,95 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHEXCEPTION_P_H
+#define SSHEXCEPTION_P_H
+
+#include "ssherrors.h"
+
+#include <QByteArray>
+#include <QCoreApplication>
+#include <QString>
+
+#include <exception>
+
+namespace QSsh {
+namespace Internal {
+
+enum SshErrorCode {
+    SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1,
+    SSH_DISCONNECT_PROTOCOL_ERROR = 2,
+    SSH_DISCONNECT_KEY_EXCHANGE_FAILED = 3,
+    SSH_DISCONNECT_RESERVED = 4,
+    SSH_DISCONNECT_MAC_ERROR = 5,
+    SSH_DISCONNECT_COMPRESSION_ERROR = 6,
+    SSH_DISCONNECT_SERVICE_NOT_AVAILABLE = 7,
+    SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8,
+    SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9,
+    SSH_DISCONNECT_CONNECTION_LOST = 10,
+    SSH_DISCONNECT_BY_APPLICATION = 11,
+    SSH_DISCONNECT_TOO_MANY_CONNECTIONS = 12,
+    SSH_DISCONNECT_AUTH_CANCELLED_BY_USER = 13,
+    SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14,
+    SSH_DISCONNECT_ILLEGAL_USER_NAME = 15
+};
+
+#define SSH_TR(string) QCoreApplication::translate("SshConnection", string)
+
+#define SSH_SERVER_EXCEPTION(error, errorString)                              \
+    SshServerException((error), (errorString), SSH_TR(errorString))
+
+struct SshServerException : public std::exception
+{
+    SshServerException(SshErrorCode error, const QByteArray &errorStringServer,
+        const QString &errorStringUser)
+        : error(error), errorStringServer(errorStringServer),
+          errorStringUser(errorStringUser) {}
+    const char *what() const noexcept override { return errorStringServer.constData(); }
+
+    const SshErrorCode error;
+    const QByteArray errorStringServer;
+    const QString errorStringUser;
+};
+
+struct SshClientException : public std::exception
+{
+    SshClientException(SshError error, const QString &errorString)
+        : error(error), errorString(errorString), errorStringPrintable(errorString.toLocal8Bit()) {}
+    const char *what() const noexcept override { return errorStringPrintable.constData(); }
+
+    const SshError error;
+    const QString errorString;
+    const QByteArray errorStringPrintable;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHEXCEPTION_P_H

+ 100 - 0
src/tool/serverctrl/qssh/sshforwardedtcpiptunnel.cpp

@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "sshforwardedtcpiptunnel.h"
+#include "sshforwardedtcpiptunnel_p.h"
+#include "sshlogging_p.h"
+#include "sshsendfacility_p.h"
+
+namespace QSsh {
+
+namespace Internal {
+SshForwardedTcpIpTunnelPrivate::SshForwardedTcpIpTunnelPrivate(quint32 channelId,
+                                                               SshSendFacility &sendFacility) :
+    SshTcpIpTunnelPrivate(channelId, sendFacility)
+{
+    setChannelState(SessionRequested);
+}
+
+void SshForwardedTcpIpTunnelPrivate::handleOpenSuccessInternal()
+{
+    QSSH_ASSERT_AND_RETURN(channelState() == AbstractSshChannel::SessionEstablished);
+
+    try {
+        m_sendFacility.sendChannelOpenConfirmationPacket(remoteChannel(), localChannelId(),
+                                                         initialWindowSize(), maxPacketSize());
+    } catch (const std::exception &e) { // Won't happen, but let's play it safe.
+        qCWarning(sshLog, "Botan error: %s", e.what());
+        closeChannel();
+    }
+}
+
+} // namespace Internal
+
+using namespace Internal;
+
+SshForwardedTcpIpTunnel::SshForwardedTcpIpTunnel(quint32 channelId, SshSendFacility &sendFacility) :
+    d(new SshForwardedTcpIpTunnelPrivate(channelId, sendFacility))
+{
+    d->init(this);
+}
+
+SshForwardedTcpIpTunnel::~SshForwardedTcpIpTunnel()
+{
+    delete d;
+}
+
+bool SshForwardedTcpIpTunnel::atEnd() const
+{
+    return QIODevice::atEnd() && d->m_data.isEmpty();
+}
+
+qint64 SshForwardedTcpIpTunnel::bytesAvailable() const
+{
+    return QIODevice::bytesAvailable() + d->m_data.count();
+}
+
+bool SshForwardedTcpIpTunnel::canReadLine() const
+{
+    return QIODevice::canReadLine() || d->m_data.contains('\n');
+}
+
+void SshForwardedTcpIpTunnel::close()
+{
+    d->closeChannel();
+    QIODevice::close();
+}
+
+qint64 SshForwardedTcpIpTunnel::readData(char *data, qint64 maxlen)
+{
+    return d->readData(data, maxlen);
+}
+
+qint64 SshForwardedTcpIpTunnel::writeData(const char *data, qint64 len)
+{
+    return d->writeData(data, len);
+}
+
+} // namespace QSsh

+ 70 - 0
src/tool/serverctrl/qssh/sshforwardedtcpiptunnel.h

@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#pragma once
+
+#include "ssh_global.h"
+#include <QIODevice>
+#include <QSharedPointer>
+
+namespace QSsh {
+
+namespace Internal {
+class SshChannelManager;
+class SshForwardedTcpIpTunnelPrivate;
+class SshSendFacility;
+class SshTcpIpTunnelPrivate;
+} // namespace Internal
+
+class QSSH_EXPORT SshForwardedTcpIpTunnel : public QIODevice
+{
+    Q_OBJECT
+    friend class Internal::SshChannelManager;
+    friend class Internal::SshTcpIpTunnelPrivate;
+
+public:
+    typedef QSharedPointer<SshForwardedTcpIpTunnel> Ptr;
+    ~SshForwardedTcpIpTunnel() override;
+
+    // QIODevice stuff
+    bool atEnd() const override;
+    qint64 bytesAvailable() const override;
+    bool canReadLine() const override;
+    void close() override;
+    bool isSequential() const override { return true; }
+
+signals:
+    void error(const QString &reason);
+
+private:
+    SshForwardedTcpIpTunnel(quint32 channelId, Internal::SshSendFacility &sendFacility);
+
+    // QIODevice stuff
+    qint64 readData(char *data, qint64 maxlen) override;
+    qint64 writeData(const char *data, qint64 len) override;
+
+    Internal::SshForwardedTcpIpTunnelPrivate * const d;
+};
+
+} // namespace QSsh

+ 44 - 0
src/tool/serverctrl/qssh/sshforwardedtcpiptunnel_p.h

@@ -0,0 +1,44 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "sshforwardedtcpiptunnel.h"
+#include "sshtcpiptunnel_p.h"
+
+namespace QSsh {
+namespace Internal {
+
+class SshForwardedTcpIpTunnelPrivate : public SshTcpIpTunnelPrivate
+{
+    Q_OBJECT
+    friend class QSsh::SshForwardedTcpIpTunnel;
+public:
+    SshForwardedTcpIpTunnelPrivate(quint32 channelId, SshSendFacility &sendFacility);
+    void handleOpenSuccessInternal() override;
+};
+
+} // namespace Internal
+} // namespace QSsh

+ 127 - 0
src/tool/serverctrl/qssh/sshhostkeydatabase.cpp

@@ -0,0 +1,127 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://www.qt.io/licensing.  For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+#include "sshhostkeydatabase.h"
+
+#include "sshlogging_p.h"
+
+#include <QByteArray>
+#include <QCoreApplication>
+#include <QDir>
+#include <QFile>
+#include <QHash>
+#include <QString>
+
+namespace QSsh {
+
+class SshHostKeyDatabase::SshHostKeyDatabasePrivate
+{
+public:
+    QHash<QString, QByteArray> hostKeys;
+};
+
+SshHostKeyDatabase::SshHostKeyDatabase() : d(new SshHostKeyDatabasePrivate)
+{
+}
+
+SshHostKeyDatabase::~SshHostKeyDatabase()
+{
+    delete d;
+}
+
+bool SshHostKeyDatabase::load(const QString &filePath, QString *error)
+{
+    QFile file(filePath);
+    if (!file.open(QIODevice::ReadOnly)) {
+        if (error) {
+            *error = QCoreApplication::translate("QSsh::Ssh",
+                                                 "Failed to open key file \"%1\" for reading: %2")
+                    .arg(QDir::toNativeSeparators(filePath), file.errorString());
+        }
+        return false;
+    }
+
+    d->hostKeys.clear();
+    const QByteArray content = file.readAll().trimmed();
+    if (content.isEmpty())
+        return true;
+    for (const QByteArray &line : content.split('\n')) {
+        const QList<QByteArray> &lineData = line.trimmed().split(' ');
+        if (lineData.count() != 2) {
+            qCDebug(Internal::sshLog, "Unexpected line \"%s\" in file \"%s\".", line.constData(),
+                   qPrintable(filePath));
+            continue;
+        }
+        d->hostKeys.insert(QString::fromUtf8(lineData.first()),
+                           QByteArray::fromHex(lineData.last()));
+    }
+
+    return true;
+}
+
+bool SshHostKeyDatabase::store(const QString &filePath, QString *error) const
+{
+    QFile file(filePath);
+    if (!file.open(QIODevice::WriteOnly)) {
+        if (error) {
+            *error = QCoreApplication::translate("QSsh::Ssh",
+                                                 "Failed to open key file \"%1\" for writing: %2")
+                    .arg(QDir::toNativeSeparators(filePath), file.errorString());
+        }
+        return false;
+    }
+
+    file.resize(0);
+    for (auto it = d->hostKeys.constBegin(); it != d->hostKeys.constEnd(); ++it)
+        file.write(it.key().toUtf8() + ' ' + it.value().toHex() + '\n');
+    return true;
+}
+
+SshHostKeyDatabase::KeyLookupResult SshHostKeyDatabase::matchHostKey(const QString &hostName,
+                                                                     const QByteArray &key) const
+{
+    auto it = d->hostKeys.constFind(hostName);
+    if (it == d->hostKeys.constEnd())
+        return KeyLookupNoMatch;
+    if (it.value() == key)
+        return KeyLookupMatch;
+    return KeyLookupMismatch;
+}
+
+void SshHostKeyDatabase::insertHostKey(const QString &hostName, const QByteArray &key)
+{
+    d->hostKeys.insert(hostName, key);
+}
+
+QByteArray SshHostKeyDatabase::retrieveHostKey(const QString &hostName)
+{
+   return d->hostKeys.value(hostName);
+}
+
+} // namespace QSsh

+ 75 - 0
src/tool/serverctrl/qssh/sshhostkeydatabase.h

@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://www.qt.io/licensing.  For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+#ifndef SSHHOSTKEYDATABASE_H
+#define SSHHOSTKEYDATABASE_H
+
+#include "ssh_global.h"
+
+#include <QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QByteArray;
+class QString;
+QT_END_NAMESPACE
+
+namespace QSsh {
+class SshHostKeyDatabase;
+
+/// Convenience typedef
+typedef QSharedPointer<SshHostKeyDatabase> SshHostKeyDatabasePtr;
+
+class QSSH_EXPORT SshHostKeyDatabase
+{
+    friend class QSharedPointer<SshHostKeyDatabase>; // To give create() access to our constructor.
+
+public:
+    enum KeyLookupResult {
+        KeyLookupMatch,
+        KeyLookupNoMatch,
+        KeyLookupMismatch
+    };
+
+    SshHostKeyDatabase();
+    ~SshHostKeyDatabase();
+
+    bool load(const QString &filePath, QString *error = nullptr);
+    bool store(const QString &filePath, QString *error = nullptr) const;
+    KeyLookupResult matchHostKey(const QString &hostName, const QByteArray &key) const;
+    void insertHostKey(const QString &hostName, const QByteArray &key);
+    QByteArray retrieveHostKey(const QString &hostName);
+
+private:
+    class SshHostKeyDatabasePrivate;
+    SshHostKeyDatabasePrivate * const d;
+};
+
+} // namespace QSsh
+
+#endif // Include guard.

+ 609 - 0
src/tool/serverctrl/qssh/sshincomingpacket.cpp

@@ -0,0 +1,609 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshincomingpacket_p.h"
+
+#include "ssh_global.h"
+#include "sshbotanconversions_p.h"
+#include "sshcapabilities_p.h"
+#include "sshlogging_p.h"
+
+namespace QSsh {
+namespace Internal {
+
+const QByteArray SshIncomingPacket::ExitStatusType("exit-status");
+const QByteArray SshIncomingPacket::ExitSignalType("exit-signal");
+const QByteArray SshIncomingPacket::ForwardedTcpIpType("forwarded-tcpip");
+
+SshIncomingPacket::SshIncomingPacket() : m_serverSeqNr(0) { }
+
+quint32 SshIncomingPacket::cipherBlockSize() const
+{
+    return qMax(m_decrypter.cipherBlockSize(), 8U);
+}
+
+quint32 SshIncomingPacket::macLength() const
+{
+    return m_decrypter.macLength();
+}
+
+void SshIncomingPacket::recreateKeys(const SshKeyExchange &keyExchange)
+{
+    m_decrypter.recreateKeys(keyExchange);
+}
+
+void SshIncomingPacket::reset()
+{
+    clear();
+    m_serverSeqNr = 0;
+    m_decrypter.clearKeys();
+}
+
+void SshIncomingPacket::consumeData(QByteArray &newData)
+{
+    qCDebug(sshLog, "%s: current data size = %d, new data size = %d",
+        Q_FUNC_INFO, int(m_data.size()), int(newData.size()));
+
+    if (isComplete() || newData.isEmpty())
+        return;
+
+    /*
+     * Until we have reached the minimum packet size, we cannot decrypt the
+     * length field.
+     */
+    const quint32 minSize = minPacketSize();
+    if (currentDataSize() < minSize) {
+        const int bytesToTake
+            = qMin<quint32>(minSize - currentDataSize(), newData.size());
+        moveFirstBytes(m_data, newData, bytesToTake);
+        qCDebug(sshLog, "Took %d bytes from new data", bytesToTake);
+        if (currentDataSize() < minSize)
+            return;
+    }
+
+    if (4 + length() + macLength() < currentDataSize())
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, "Server sent invalid packet.");
+
+    const int bytesToTake
+        = qMin<quint32>(length() + 4 + macLength() - currentDataSize(),
+              newData.size());
+    moveFirstBytes(m_data, newData, bytesToTake);
+    qCDebug(sshLog, "Took %d bytes from new data", bytesToTake);
+    if (isComplete()) {
+        qCDebug(sshLog, "Message complete. Overall size: %u, payload size: %u",
+            int(m_data.size()), m_length - paddingLength() - 1);
+        decrypt();
+        ++m_serverSeqNr;
+    }
+}
+
+void SshIncomingPacket::decrypt()
+{
+    Q_ASSERT(isComplete());
+    const quint32 netDataLength = length() + 4;
+    m_decrypter.decrypt(m_data, cipherBlockSize(),
+        netDataLength - cipherBlockSize());
+    const QByteArray &mac = m_data.mid(netDataLength, macLength());
+    if (mac != generateMac(m_decrypter, m_serverSeqNr)) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_MAC_ERROR,
+                           "Message authentication failed.");
+    }
+}
+
+void SshIncomingPacket::moveFirstBytes(QByteArray &target, QByteArray &source,
+    int n)
+{
+    target.append(source.left(n));
+    source.remove(0, n);
+}
+
+SshKeyExchangeInit SshIncomingPacket::extractKeyExchangeInitData() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_KEXINIT);
+
+    SshKeyExchangeInit exchangeData;
+    try {
+        quint32 offset = TypeOffset + 1;
+        std::memcpy(exchangeData.cookie, &m_data.constData()[offset],
+                    sizeof exchangeData.cookie);
+        offset += sizeof exchangeData.cookie;
+        exchangeData.keyAlgorithms
+            = SshPacketParser::asNameList(m_data, &offset);
+        exchangeData.serverHostKeyAlgorithms
+            = SshPacketParser::asNameList(m_data, &offset);
+        exchangeData.encryptionAlgorithmsClientToServer
+            = SshPacketParser::asNameList(m_data, &offset);
+        exchangeData.encryptionAlgorithmsServerToClient
+            = SshPacketParser::asNameList(m_data, &offset);
+        exchangeData.macAlgorithmsClientToServer
+            = SshPacketParser::asNameList(m_data, &offset);
+        exchangeData.macAlgorithmsServerToClient
+            = SshPacketParser::asNameList(m_data, &offset);
+        exchangeData.compressionAlgorithmsClientToServer
+            = SshPacketParser::asNameList(m_data, &offset);
+        exchangeData.compressionAlgorithmsServerToClient
+            = SshPacketParser::asNameList(m_data, &offset);
+        exchangeData.languagesClientToServer
+            = SshPacketParser::asNameList(m_data, &offset);
+        exchangeData.languagesServerToClient
+            = SshPacketParser::asNameList(m_data, &offset);
+        exchangeData.firstKexPacketFollows
+            = SshPacketParser::asBool(m_data, &offset);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+            "Key exchange failed: Server sent invalid SSH_MSG_KEXINIT packet.");
+    }
+    return exchangeData;
+}
+
+static void getHostKeySpecificReplyData(SshKeyExchangeReply &replyData,
+                                        const QByteArray &hostKeyAlgo, const QByteArray &input)
+{
+    quint32 offset = 0;
+    if (hostKeyAlgo == SshCapabilities::PubKeyDss || hostKeyAlgo == SshCapabilities::PubKeyRsa) {
+        // DSS: p and q, RSA: e and n
+        replyData.hostKeyParameters << SshPacketParser::asBigInt(input, &offset);
+        replyData.hostKeyParameters << SshPacketParser::asBigInt(input, &offset);
+
+        // g and y
+        if (hostKeyAlgo == SshCapabilities::PubKeyDss) {
+            replyData.hostKeyParameters << SshPacketParser::asBigInt(input, &offset);
+            replyData.hostKeyParameters << SshPacketParser::asBigInt(input, &offset);
+        }
+    } else {
+        QSSH_ASSERT_AND_RETURN(hostKeyAlgo.startsWith(SshCapabilities::PubKeyEcdsaPrefix));
+        if (SshPacketParser::asString(input, &offset)
+                != hostKeyAlgo.mid(11)) { // Without "ecdsa-sha2-" prefix.
+            throw SshPacketParseException();
+        }
+        replyData.q = SshPacketParser::asString(input, &offset);
+    }
+}
+
+static QByteArray &padToWidth(QByteArray &data, int targetWidth)
+{
+    return data.prepend(QByteArray(targetWidth - data.count(), 0));
+}
+
+SshKeyExchangeReply SshIncomingPacket::extractKeyExchangeReply(const QByteArray &kexAlgo,
+                                                               const QByteArray &hostKeyAlgo) const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_KEXDH_REPLY);
+
+    try {
+        SshKeyExchangeReply replyData;
+        quint32 topLevelOffset = TypeOffset + 1;
+        replyData.k_s = SshPacketParser::asString(m_data, &topLevelOffset);
+        quint32 k_sOffset = 0;
+        if (SshPacketParser::asString(replyData.k_s, &k_sOffset) != hostKeyAlgo)
+            throw SshPacketParseException();
+        getHostKeySpecificReplyData(replyData, hostKeyAlgo, replyData.k_s.mid(k_sOffset));
+
+        if (kexAlgo == SshCapabilities::DiffieHellmanGroup1Sha1
+                || kexAlgo == SshCapabilities::DiffieHellmanGroup14Sha1) {
+            replyData.f = SshPacketParser::asBigInt(m_data, &topLevelOffset);
+        } else {
+            QSSH_ASSERT_AND_RETURN_VALUE(kexAlgo.startsWith(SshCapabilities::EcdhKexNamePrefix),
+                                         SshKeyExchangeReply());
+            replyData.q_s = SshPacketParser::asString(m_data, &topLevelOffset);
+        }
+        const QByteArray fullSignature = SshPacketParser::asString(m_data, &topLevelOffset);
+        quint32 sigOffset = 0;
+        if (SshPacketParser::asString(fullSignature, &sigOffset) != hostKeyAlgo)
+            throw SshPacketParseException();
+        replyData.signatureBlob = SshPacketParser::asString(fullSignature, &sigOffset);
+        if (hostKeyAlgo.startsWith(SshCapabilities::PubKeyEcdsaPrefix)) {
+            // Botan's PK_Verifier wants the signature in this format.
+            quint32 blobOffset = 0;
+            const Botan::BigInt r = SshPacketParser::asBigInt(replyData.signatureBlob, &blobOffset);
+            const Botan::BigInt s = SshPacketParser::asBigInt(replyData.signatureBlob, &blobOffset);
+            const int width = SshCapabilities::ecdsaIntegerWidthInBytes(hostKeyAlgo);
+            QByteArray encodedR = convertByteArray(Botan::BigInt::encode(r));
+            replyData.signatureBlob = padToWidth(encodedR, width);
+            QByteArray encodedS = convertByteArray(Botan::BigInt::encode(s));
+            replyData.signatureBlob += padToWidth(encodedS, width);
+        }
+        replyData.k_s.prepend(m_data.mid(TypeOffset + 1, 4));
+        return replyData;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+            "Key exchange failed: "
+            "Server sent invalid key exchange reply packet.");
+    }
+}
+
+SshDisconnect SshIncomingPacket::extractDisconnect() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_DISCONNECT);
+
+    SshDisconnect msg;
+    try {
+        quint32 offset = TypeOffset + 1;
+        msg.reasonCode = SshPacketParser::asUint32(m_data, &offset);
+        msg.description = SshPacketParser::asUserString(m_data, &offset);
+        msg.language = SshPacketParser::asString(m_data, &offset);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_MSG_DISCONNECT.");
+    }
+
+    return msg;
+}
+
+SshUserAuthBanner SshIncomingPacket::extractUserAuthBanner() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_USERAUTH_BANNER);
+
+    try {
+        SshUserAuthBanner msg;
+        quint32 offset = TypeOffset + 1;
+        msg.message = SshPacketParser::asUserString(m_data, &offset);
+        msg.language = SshPacketParser::asString(m_data, &offset);
+        return msg;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_MSG_USERAUTH_BANNER.");
+    }
+}
+
+SshUserAuthInfoRequestPacket SshIncomingPacket::extractUserAuthInfoRequest() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_USERAUTH_INFO_REQUEST);
+
+    try {
+        SshUserAuthInfoRequestPacket msg;
+        quint32 offset = TypeOffset + 1;
+        msg.name = SshPacketParser::asUserString(m_data, &offset);
+        msg.instruction = SshPacketParser::asUserString(m_data, &offset);
+        msg.languageTag = SshPacketParser::asString(m_data, &offset);
+        const quint32 promptCount = SshPacketParser::asUint32(m_data, &offset);
+        msg.prompts.reserve(promptCount);
+        msg.echos.reserve(promptCount);
+        for (quint32 i = 0; i < promptCount; ++i) {
+            msg.prompts << SshPacketParser::asUserString(m_data, &offset);
+            msg.echos << SshPacketParser::asBool(m_data, &offset);
+        }
+        return msg;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_MSG_USERAUTH_INFO_REQUEST.");
+    }
+}
+
+SshUserAuthPkOkPacket SshIncomingPacket::extractUserAuthPkOk() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_USERAUTH_PK_OK);
+
+    try {
+        SshUserAuthPkOkPacket msg;
+        quint32 offset = TypeOffset + 1;
+        msg.algoName= SshPacketParser::asString(m_data, &offset);
+        msg.keyBlob = SshPacketParser::asString(m_data, &offset);
+        return msg;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_MSG_USERAUTH_PK_OK.");
+    }
+}
+
+SshDebug SshIncomingPacket::extractDebug() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_DEBUG);
+
+    try {
+        SshDebug msg;
+        quint32 offset = TypeOffset + 1;
+        msg.display = SshPacketParser::asBool(m_data, &offset);
+        msg.message = SshPacketParser::asUserString(m_data, &offset);
+        msg.language = SshPacketParser::asString(m_data, &offset);
+        return msg;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_MSG_DEBUG.");
+    }
+}
+
+SshRequestSuccess SshIncomingPacket::extractRequestSuccess() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_REQUEST_SUCCESS);
+
+    try {
+        SshRequestSuccess msg;
+        quint32 offset = TypeOffset + 1;
+        msg.bindPort = SshPacketParser::asUint32(m_data, &offset);
+        return msg;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_MSG_REQUEST_SUCCESS.");
+    }
+}
+
+SshUnimplemented SshIncomingPacket::extractUnimplemented() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_UNIMPLEMENTED);
+
+    try {
+        SshUnimplemented msg;
+        quint32 offset = TypeOffset + 1;
+        msg.invalidMsgSeqNr = SshPacketParser::asUint32(m_data, &offset);
+        return msg;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_MSG_UNIMPLEMENTED.");
+    }
+}
+
+SshChannelOpenGeneric SshIncomingPacket::extractChannelOpen() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_CHANNEL_OPEN);
+
+    try {
+        SshChannelOpenGeneric channelOpen;
+        quint32 offset = TypeOffset + 1;
+        channelOpen.channelType = SshPacketParser::asString(m_data, &offset);
+        channelOpen.commonData.remoteChannel = SshPacketParser::asUint32(m_data, &offset);
+        channelOpen.commonData.remoteWindowSize = SshPacketParser::asUint32(m_data, &offset);
+        channelOpen.commonData.remoteMaxPacketSize = SshPacketParser::asUint32(m_data, &offset);
+        channelOpen.typeSpecificData = m_data.mid(offset, length() - paddingLength() - offset
+                                                  + int(sizeof m_length));
+        return channelOpen;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Server sent invalid SSH_MSG_CHANNEL_OPEN packet.");
+    }
+}
+
+SshChannelOpenForwardedTcpIp SshIncomingPacket::extractChannelOpenForwardedTcpIp(
+        const SshChannelOpenGeneric &genericData)
+{
+    try {
+        SshChannelOpenForwardedTcpIp specificData;
+        specificData.common = genericData.commonData;
+        quint32 offset = 0;
+        specificData.remoteAddress = SshPacketParser::asString(genericData.typeSpecificData,
+                                                               &offset);
+        specificData.remotePort = SshPacketParser::asUint32(genericData.typeSpecificData, &offset);
+        specificData.originatorAddress = SshPacketParser::asString(genericData.typeSpecificData,
+                                                                   &offset);
+        specificData.originatorPort = SshPacketParser::asUint32(genericData.typeSpecificData,
+                                                                &offset);
+        return specificData;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Server sent invalid SSH_MSG_CHANNEL_OPEN packet.");
+    }
+}
+
+SshChannelOpenX11 SshIncomingPacket::extractChannelOpenX11(const SshChannelOpenGeneric &genericData)
+{
+    try {
+        SshChannelOpenX11 specificData;
+        specificData.common = genericData.commonData;
+        quint32 offset = 0;
+        specificData.originatorAddress = SshPacketParser::asString(genericData.typeSpecificData,
+                                                                   &offset);
+        specificData.originatorPort = SshPacketParser::asUint32(genericData.typeSpecificData,
+                                                                &offset);
+        return specificData;
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Server sent invalid SSH_MSG_CHANNEL_OPEN packet.");
+    }
+}
+
+SshChannelOpenFailure SshIncomingPacket::extractChannelOpenFailure() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_CHANNEL_OPEN_FAILURE);
+
+    SshChannelOpenFailure openFailure;
+    try {
+        quint32 offset = TypeOffset + 1;
+        openFailure.localChannel = SshPacketParser::asUint32(m_data, &offset);
+        openFailure.reasonCode = SshPacketParser::asUint32(m_data, &offset);
+        openFailure.reasonString = QString::fromLocal8Bit(SshPacketParser::asString(m_data, &offset));
+        openFailure.language = SshPacketParser::asString(m_data, &offset);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Server sent invalid SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
+    }
+    return openFailure;
+}
+
+SshChannelOpenConfirmation SshIncomingPacket::extractChannelOpenConfirmation() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_CHANNEL_OPEN_CONFIRMATION);
+
+    SshChannelOpenConfirmation confirmation;
+    try {
+        quint32 offset = TypeOffset + 1;
+        confirmation.localChannel = SshPacketParser::asUint32(m_data, &offset);
+        confirmation.remoteChannel = SshPacketParser::asUint32(m_data, &offset);
+        confirmation.remoteWindowSize = SshPacketParser::asUint32(m_data, &offset);
+        confirmation.remoteMaxPacketSize = SshPacketParser::asUint32(m_data, &offset);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Server sent invalid SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet.");
+    }
+    return confirmation;
+}
+
+SshChannelWindowAdjust SshIncomingPacket::extractWindowAdjust() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_CHANNEL_WINDOW_ADJUST);
+
+    SshChannelWindowAdjust adjust;
+    try {
+        quint32 offset = TypeOffset + 1;
+        adjust.localChannel = SshPacketParser::asUint32(m_data, &offset);
+        adjust.bytesToAdd = SshPacketParser::asUint32(m_data, &offset);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_MSG_CHANNEL_WINDOW_ADJUST packet.");
+    }
+    return adjust;
+}
+
+SshChannelData SshIncomingPacket::extractChannelData() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_CHANNEL_DATA);
+
+    SshChannelData data;
+    try {
+        quint32 offset = TypeOffset + 1;
+        data.localChannel = SshPacketParser::asUint32(m_data, &offset);
+        data.data = SshPacketParser::asString(m_data, &offset);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_MSG_CHANNEL_DATA packet.");
+    }
+    return data;
+}
+
+SshChannelExtendedData SshIncomingPacket::extractChannelExtendedData() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_CHANNEL_EXTENDED_DATA);
+
+    SshChannelExtendedData data;
+    try {
+        quint32 offset = TypeOffset + 1;
+        data.localChannel = SshPacketParser::asUint32(m_data, &offset);
+        data.type = SshPacketParser::asUint32(m_data, &offset);
+        data.data = SshPacketParser::asString(m_data, &offset);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_MSG_CHANNEL_EXTENDED_DATA packet.");
+    }
+    return data;
+}
+
+SshChannelExitStatus SshIncomingPacket::extractChannelExitStatus() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_CHANNEL_REQUEST);
+
+    SshChannelExitStatus exitStatus;
+    try {
+        quint32 offset = TypeOffset + 1;
+        exitStatus.localChannel = SshPacketParser::asUint32(m_data, &offset);
+        const QByteArray &type = SshPacketParser::asString(m_data, &offset);
+        Q_ASSERT(type == ExitStatusType);
+        Q_UNUSED(type);
+        if (SshPacketParser::asBool(m_data, &offset))
+            throw SshPacketParseException();
+        exitStatus.exitStatus = SshPacketParser::asUint32(m_data, &offset);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid exit-status packet.");
+    }
+    return exitStatus;
+}
+
+SshChannelExitSignal SshIncomingPacket::extractChannelExitSignal() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_CHANNEL_REQUEST);
+
+    SshChannelExitSignal exitSignal;
+    try {
+        quint32 offset = TypeOffset + 1;
+        exitSignal.localChannel = SshPacketParser::asUint32(m_data, &offset);
+        const QByteArray &type = SshPacketParser::asString(m_data, &offset);
+        Q_ASSERT(type == ExitSignalType);
+        Q_UNUSED(type);
+        if (SshPacketParser::asBool(m_data, &offset))
+            throw SshPacketParseException();
+        exitSignal.signal = SshPacketParser::asString(m_data, &offset);
+        exitSignal.coreDumped = SshPacketParser::asBool(m_data, &offset);
+        exitSignal.error = SshPacketParser::asUserString(m_data, &offset);
+        exitSignal.language = SshPacketParser::asString(m_data, &offset);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid exit-signal packet.");
+    }
+    return exitSignal;
+}
+
+quint32 SshIncomingPacket::extractRecipientChannel() const
+{
+    Q_ASSERT(isComplete());
+
+    try {
+        quint32 offset = TypeOffset + 1;
+        return SshPacketParser::asUint32(m_data, &offset);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Server sent invalid packet.");
+    }
+}
+
+QByteArray SshIncomingPacket::extractChannelRequestType() const
+{
+    Q_ASSERT(isComplete());
+    Q_ASSERT(type() == SSH_MSG_CHANNEL_REQUEST);
+
+    try {
+        quint32 offset = TypeOffset + 1;
+        SshPacketParser::asUint32(m_data, &offset);
+        return SshPacketParser::asString(m_data, &offset);
+    } catch (const SshPacketParseException &) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Invalid SSH_MSG_CHANNEL_REQUEST packet.");
+    }
+}
+
+void SshIncomingPacket::calculateLength() const
+{
+    Q_ASSERT(currentDataSize() >= minPacketSize());
+    qCDebug(sshLog, "Length field before decryption: %d-%d-%d-%d", m_data.at(0) & 0xff,
+        m_data.at(1) & 0xff, m_data.at(2) & 0xff, m_data.at(3) & 0xff);
+    m_decrypter.decrypt(m_data, 0, cipherBlockSize());
+    qCDebug(sshLog, "Length field after decryption: %d-%d-%d-%d", m_data.at(0) & 0xff, m_data.at(1) & 0xff, m_data.at(2) & 0xff, m_data.at(3) & 0xff);
+    qCDebug(sshLog, "message type = %d", m_data.at(TypeOffset));
+    m_length = SshPacketParser::asUint32(m_data, static_cast<quint32>(0));
+    qCDebug(sshLog, "decrypted length is %u", m_length);
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 252 - 0
src/tool/serverctrl/qssh/sshincomingpacket_p.h

@@ -0,0 +1,252 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHINCOMINGPACKET_P_H
+#define SSHINCOMINGPACKET_P_H
+
+#include "sshpacket_p.h"
+
+#include "sshcryptofacility_p.h"
+#include "sshpacketparser_p.h"
+
+#include <QStringList>
+
+namespace QSsh {
+namespace Internal {
+
+class SshKeyExchange;
+
+struct SshKeyExchangeInit
+{
+    char cookie[16];
+    SshNameList keyAlgorithms;
+    SshNameList serverHostKeyAlgorithms;
+    SshNameList encryptionAlgorithmsClientToServer;
+    SshNameList encryptionAlgorithmsServerToClient;
+    SshNameList macAlgorithmsClientToServer;
+    SshNameList macAlgorithmsServerToClient;
+    SshNameList compressionAlgorithmsClientToServer;
+    SshNameList compressionAlgorithmsServerToClient;
+    SshNameList languagesClientToServer;
+    SshNameList languagesServerToClient;
+    bool firstKexPacketFollows;
+};
+
+struct SshKeyExchangeReply
+{
+    QByteArray k_s;
+    QList<Botan::BigInt> hostKeyParameters; // DSS: p, q, g, y. RSA: e, n.
+    QByteArray q; // For ECDSA host keys only.
+    Botan::BigInt f; // For DH only.
+    QByteArray q_s; // For ECDH only.
+    QByteArray signatureBlob;
+};
+
+struct SshDisconnect
+{
+    quint32 reasonCode;
+    QString description;
+    QByteArray language;
+};
+
+struct SshUserAuthBanner
+{
+    QString message;
+    QByteArray language;
+};
+
+struct SshUserAuthPkOkPacket
+{
+    QByteArray algoName;
+    QByteArray keyBlob;
+};
+
+struct SshUserAuthInfoRequestPacket
+{
+    QString name;
+    QString instruction;
+    QByteArray languageTag;
+    QStringList prompts;
+    QList<bool> echos;
+};
+
+struct SshDebug
+{
+    bool display;
+    QString message;
+    QByteArray language;
+};
+
+struct SshUnimplemented
+{
+    quint32 invalidMsgSeqNr;
+};
+
+struct SshRequestSuccess
+{
+    quint32 bindPort;
+};
+
+struct SshChannelOpenCommon
+{
+    quint32 remoteChannel;
+    quint32 remoteWindowSize;
+    quint32 remoteMaxPacketSize;
+};
+
+struct SshChannelOpenGeneric
+{
+    QByteArray channelType;
+    SshChannelOpenCommon commonData;
+    QByteArray typeSpecificData;
+};
+
+struct SshChannelOpenForwardedTcpIp
+{
+    SshChannelOpenCommon common;
+    QByteArray remoteAddress;
+    quint32 remotePort;
+    QByteArray originatorAddress;
+    quint32 originatorPort;
+};
+
+struct SshChannelOpenX11
+{
+    SshChannelOpenCommon common;
+    QByteArray originatorAddress;
+    quint32 originatorPort;
+};
+
+struct SshChannelOpenFailure
+{
+    quint32 localChannel;
+    quint32 reasonCode;
+    QString reasonString;
+    QByteArray language;
+};
+
+struct SshChannelOpenConfirmation
+{
+    quint32 localChannel;
+    quint32 remoteChannel;
+    quint32 remoteWindowSize;
+    quint32 remoteMaxPacketSize;
+};
+
+struct SshChannelWindowAdjust
+{
+    quint32 localChannel;
+    quint32 bytesToAdd;
+};
+
+struct SshChannelData
+{
+    quint32 localChannel;
+    QByteArray data;
+};
+
+struct SshChannelExtendedData
+{
+    quint32 localChannel;
+    quint32 type;
+    QByteArray data;
+};
+
+struct SshChannelExitStatus
+{
+    quint32 localChannel;
+    quint32 exitStatus;
+};
+
+struct SshChannelExitSignal
+{
+    quint32 localChannel;
+    QByteArray signal;
+    bool coreDumped;
+    QString error;
+    QByteArray language;
+};
+
+class SshIncomingPacket : public AbstractSshPacket
+{
+public:
+    SshIncomingPacket();
+
+    void consumeData(QByteArray &data);
+    void recreateKeys(const SshKeyExchange &keyExchange);
+    void reset();
+
+    SshKeyExchangeInit extractKeyExchangeInitData() const;
+    SshKeyExchangeReply extractKeyExchangeReply(const QByteArray &kexAlgo,
+                                                const QByteArray &hostKeyAlgo) const;
+    SshDisconnect extractDisconnect() const;
+    SshUserAuthBanner extractUserAuthBanner() const;
+    SshUserAuthInfoRequestPacket extractUserAuthInfoRequest() const;
+    SshUserAuthPkOkPacket extractUserAuthPkOk() const;
+    SshDebug extractDebug() const;
+    SshRequestSuccess extractRequestSuccess() const;
+    SshUnimplemented extractUnimplemented() const;
+
+    SshChannelOpenGeneric extractChannelOpen() const;
+    static SshChannelOpenForwardedTcpIp extractChannelOpenForwardedTcpIp(
+            const SshChannelOpenGeneric &genericData);
+    static SshChannelOpenX11 extractChannelOpenX11(const SshChannelOpenGeneric &genericData);
+    SshChannelOpenFailure extractChannelOpenFailure() const;
+    SshChannelOpenConfirmation extractChannelOpenConfirmation() const;
+    SshChannelWindowAdjust extractWindowAdjust() const;
+    SshChannelData extractChannelData() const;
+    SshChannelExtendedData extractChannelExtendedData() const;
+    SshChannelExitStatus extractChannelExitStatus() const;
+    SshChannelExitSignal extractChannelExitSignal() const;
+    quint32 extractRecipientChannel() const;
+    QByteArray extractChannelRequestType() const;
+
+    quint32 serverSeqNr() const { return m_serverSeqNr; }
+
+    static const QByteArray ExitStatusType;
+    static const QByteArray ExitSignalType;
+    static const QByteArray ForwardedTcpIpType;
+
+private:
+    virtual quint32 cipherBlockSize() const;
+    virtual quint32 macLength() const;
+    virtual void calculateLength() const;
+
+    void decrypt();
+    void moveFirstBytes(QByteArray &target, QByteArray &source, int n);
+
+    quint32 m_serverSeqNr;
+    SshDecryptionFacility m_decrypter;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHINCOMINGPACKET_P_H

+ 301 - 0
src/tool/serverctrl/qssh/sshkeyexchange.cpp

@@ -0,0 +1,301 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshkeyexchange_p.h"
+
+#include "ssh_global.h"
+#include "sshbotanconversions_p.h"
+#include "sshcapabilities_p.h"
+#include "sshsendfacility_p.h"
+#include "sshexception_p.h"
+#include "sshincomingpacket_p.h"
+#include "sshlogging_p.h"
+
+#include <botan/dl_group.h>
+#include <botan/dh.h>
+#include <botan/numthry.h>
+#include <botan/pubkey.h>
+#include <botan/dsa.h>
+#include <botan/rsa.h>
+#include <botan/pk_ops.h>
+#include <botan/ecdh.h>
+#include <botan/ecdsa.h>
+
+#ifdef CREATOR_SSH_DEBUG
+#include <iostream>
+#endif
+#include <string>
+
+using namespace Botan;
+
+namespace QSsh {
+namespace Internal {
+
+namespace {
+
+    // For debugging
+    void printNameList(const char *listName, const SshNameList &list)
+    {
+        qCDebug(sshLog, "%s:", listName);
+        for (const QByteArray &name : list.names) {
+            qCDebug(sshLog, "%s", name.constData());
+        }
+    }
+
+    void printData(const char *name, const QByteArray &data)
+    {
+        qCDebug(sshLog, "The client thinks the %s has length %d and is: %s", name, int(data.count()),
+                data.toHex().constData());
+    }
+
+} // anonymous namespace
+
+SshKeyExchange::SshKeyExchange(const SshConnectionParameters &connParams,
+                               SshSendFacility &sendFacility)
+    : m_connParams(connParams), m_sendFacility(sendFacility)
+{
+}
+
+SshKeyExchange::~SshKeyExchange() {}
+
+void SshKeyExchange::sendKexInitPacket(const QByteArray &serverId)
+{
+    m_serverId = serverId;
+    m_clientKexInitPayload = m_sendFacility.sendKeyExchangeInitPacket();
+}
+
+bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit)
+{
+    qCDebug(sshLog, "server requests key exchange");
+    serverKexInit.printRawBytes();
+    SshKeyExchangeInit kexInitParams
+            = serverKexInit.extractKeyExchangeInitData();
+
+    printNameList("Key Algorithms", kexInitParams.keyAlgorithms);
+    printNameList("Server Host Key Algorithms", kexInitParams.serverHostKeyAlgorithms);
+    printNameList("Encryption algorithms client to server", kexInitParams.encryptionAlgorithmsClientToServer);
+    printNameList("Encryption algorithms server to client", kexInitParams.encryptionAlgorithmsServerToClient);
+    printNameList("MAC algorithms client to server", kexInitParams.macAlgorithmsClientToServer);
+    printNameList("MAC algorithms server to client", kexInitParams.macAlgorithmsServerToClient);
+    printNameList("Compression algorithms client to server", kexInitParams.compressionAlgorithmsClientToServer);
+    printNameList("Compression algorithms client to server", kexInitParams.compressionAlgorithmsClientToServer);
+    printNameList("Languages client to server", kexInitParams.languagesClientToServer);
+    printNameList("Languages server to client", kexInitParams.languagesServerToClient);
+    qCDebug(sshLog, "First packet follows: %d", kexInitParams.firstKexPacketFollows);
+
+    m_kexAlgoName = SshCapabilities::findBestMatch(SshCapabilities::KeyExchangeMethods,
+                                                   kexInitParams.keyAlgorithms.names,
+                                                   "KeyExchange");
+    m_serverHostKeyAlgo = SshCapabilities::findBestMatch(SshCapabilities::PublicKeyAlgorithms,
+            kexInitParams.serverHostKeyAlgorithms.names, "HostKey");
+    determineHashingAlgorithm(kexInitParams, true);
+    determineHashingAlgorithm(kexInitParams, false);
+
+    m_encryptionAlgo
+        = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms,
+              kexInitParams.encryptionAlgorithmsClientToServer.names, "Encryption");
+    m_decryptionAlgo
+        = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms,
+              kexInitParams.encryptionAlgorithmsServerToClient.names, "Decryption");
+    SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms,
+        kexInitParams.compressionAlgorithmsClientToServer.names, "Compression Client to Server");
+    SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms,
+        kexInitParams.compressionAlgorithmsServerToClient.names, "Compression Server to Client");
+
+    AutoSeeded_RNG rng;
+    if (m_kexAlgoName.startsWith(SshCapabilities::EcdhKexNamePrefix)) {
+        m_ecdhKey.reset(new ECDH_PrivateKey(rng, EC_Group(botanKeyExchangeAlgoName(m_kexAlgoName))));
+        m_sendFacility.sendKeyEcdhInitPacket(convertByteArray(m_ecdhKey->public_value()));
+    } else {
+        m_dhKey.reset(new DH_PrivateKey(rng, DL_Group(botanKeyExchangeAlgoName(m_kexAlgoName))));
+        m_sendFacility.sendKeyDhInitPacket(m_dhKey->get_y());
+    }
+
+    m_serverKexInitPayload = serverKexInit.payLoad();
+    return kexInitParams.firstKexPacketFollows;
+}
+
+void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply,
+    const QByteArray &clientId)
+{
+
+    const SshKeyExchangeReply &reply
+        = dhReply.extractKeyExchangeReply(m_kexAlgoName, m_serverHostKeyAlgo);
+    if (m_dhKey && (reply.f <= 0 || reply.f >= m_dhKey->group_p())) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+            "Server sent invalid f.");
+    }
+
+    QByteArray concatenatedData = AbstractSshPacket::encodeString(clientId);
+    concatenatedData += AbstractSshPacket::encodeString(m_serverId);
+    concatenatedData += AbstractSshPacket::encodeString(m_clientKexInitPayload);
+    concatenatedData += AbstractSshPacket::encodeString(m_serverKexInitPayload);
+    concatenatedData += reply.k_s;
+
+    printData("Client Id", AbstractSshPacket::encodeString(clientId));
+    printData("Server Id", AbstractSshPacket::encodeString(m_serverId));
+    printData("Client Payload", AbstractSshPacket::encodeString(m_clientKexInitPayload));
+    printData("Server payload", AbstractSshPacket::encodeString(m_serverKexInitPayload));
+    printData("K_S", reply.k_s);
+
+    AutoSeeded_RNG rng;
+
+    SecureVector<byte> encodedK;
+    if (m_dhKey) {
+        concatenatedData += AbstractSshPacket::encodeMpInt(m_dhKey->get_y());
+        concatenatedData += AbstractSshPacket::encodeMpInt(reply.f);
+
+        std::unique_ptr<PK_Ops::Key_Agreement> dhOp = m_dhKey->create_key_agreement_op(rng, "Raw", "base");
+        std::vector<byte> encodedF = BigInt::encode(reply.f);
+        encodedK = dhOp->agree(0, encodedF.data(), encodedF.size(), nullptr, 0);
+        printData("y", AbstractSshPacket::encodeMpInt(m_dhKey->get_y()));
+        printData("f", AbstractSshPacket::encodeMpInt(reply.f));
+        m_dhKey.reset();
+    } else {
+        Q_ASSERT(m_ecdhKey);
+        concatenatedData // Q_C.
+                += AbstractSshPacket::encodeString(convertByteArray(m_ecdhKey->public_value()));
+        concatenatedData += AbstractSshPacket::encodeString(reply.q_s);
+        std::unique_ptr<PK_Ops::Key_Agreement> ecdhOp = m_ecdhKey->create_key_agreement_op(rng, "Raw", "base");
+
+        encodedK = ecdhOp->agree(0, convertByteArray(reply.q_s), reply.q_s.count(), nullptr, 0);
+        m_ecdhKey.reset();
+    }
+
+    // If we try to just use "BigInt::decode(encodedK)" clang fails to link
+    const BigInt k = BigInt::decode(encodedK.data(), encodedK.size());
+    m_k = AbstractSshPacket::encodeMpInt(k); // Roundtrip, as Botan encodes BigInts somewhat differently.
+    printData("K", m_k);
+    concatenatedData += m_k;
+    printData("Concatenated data", concatenatedData);
+
+    m_hash = HashFunction::create_or_throw(botanHMacAlgoName(hashAlgoForKexAlgo()));
+    const SecureVector<byte> &hashResult = m_hash->process(convertByteArray(concatenatedData),
+                                                           concatenatedData.size());
+    m_h = convertByteArray(hashResult);
+
+    printData("H", m_h);
+
+    QScopedPointer<Public_Key> sigKey;
+    if (m_serverHostKeyAlgo == SshCapabilities::PubKeyDss) {
+        const DL_Group group(reply.hostKeyParameters.at(0), reply.hostKeyParameters.at(1),
+            reply.hostKeyParameters.at(2));
+        DSA_PublicKey * const dsaKey
+            = new DSA_PublicKey(group, reply.hostKeyParameters.at(3));
+        sigKey.reset(dsaKey);
+    } else if (m_serverHostKeyAlgo == SshCapabilities::PubKeyRsa) {
+        RSA_PublicKey * const rsaKey
+            = new RSA_PublicKey(reply.hostKeyParameters.at(1), reply.hostKeyParameters.at(0));
+        sigKey.reset(rsaKey);
+    } else {
+        QSSH_ASSERT_AND_RETURN(m_serverHostKeyAlgo.startsWith(SshCapabilities::PubKeyEcdsaPrefix));
+        const EC_Group domain(SshCapabilities::oid(m_serverHostKeyAlgo));
+
+        const PointGFp point = domain.OS2ECP(convertByteArray(reply.q), reply.q.count());
+        ECDSA_PublicKey * const ecdsaKey = new ECDSA_PublicKey(domain, point);
+        sigKey.reset(ecdsaKey);
+    }
+
+    const byte * const botanH = convertByteArray(m_h);
+    const Botan::byte * const botanSig = convertByteArray(reply.signatureBlob);
+    PK_Verifier verifier(*sigKey, botanEmsaAlgoName(m_serverHostKeyAlgo));
+    if (!verifier.verify_message(botanH, m_h.size(), botanSig, reply.signatureBlob.size())) {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+            "Invalid signature in key exchange reply packet.");
+    }
+
+    checkHostKey(reply.k_s);
+
+    m_sendFacility.sendNewKeysPacket();
+    m_hostFingerprint = QByteArray::fromStdString(sigKey->fingerprint_public("SHA-256"));
+
+}
+
+QByteArray SshKeyExchange::hashAlgoForKexAlgo() const
+{
+    if (m_kexAlgoName == SshCapabilities::EcdhNistp256)
+        return SshCapabilities::HMacSha256;
+    if (m_kexAlgoName == SshCapabilities::EcdhNistp384)
+        return SshCapabilities::HMacSha384;
+    if (m_kexAlgoName == SshCapabilities::EcdhNistp521)
+        return SshCapabilities::HMacSha512;
+    return SshCapabilities::HMacSha1;
+}
+
+void SshKeyExchange::determineHashingAlgorithm(const SshKeyExchangeInit &kexInit,
+                                               bool serverToClient)
+{
+    QByteArray * const algo = serverToClient ? &m_s2cHMacAlgo : &m_c2sHMacAlgo;
+    const QList<QByteArray> &serverCapabilities = serverToClient
+            ? kexInit.macAlgorithmsServerToClient.names
+            : kexInit.macAlgorithmsClientToServer.names;
+    *algo = SshCapabilities::findBestMatch(SshCapabilities::MacAlgorithms,
+                                           serverCapabilities,
+                                           "MacAlgorithms");
+}
+
+void SshKeyExchange::checkHostKey(const QByteArray &hostKey)
+{
+    if (m_connParams.hostKeyCheckingMode == SshHostKeyCheckingNone) {
+        if (m_connParams.hostKeyDatabase)
+            m_connParams.hostKeyDatabase->insertHostKey(m_connParams.host(), hostKey);
+        return;
+    }
+
+    if (!m_connParams.hostKeyDatabase) {
+        throw SshClientException(SshInternalError,
+                                 SSH_TR("Host key database must exist "
+                                        "if host key checking is enabled."));
+    }
+
+    switch (m_connParams.hostKeyDatabase->matchHostKey(m_connParams.host(), hostKey)) {
+    case SshHostKeyDatabase::KeyLookupMatch:
+        return; // Nothing to do.
+    case SshHostKeyDatabase::KeyLookupMismatch:
+        if (m_connParams.hostKeyCheckingMode != SshHostKeyCheckingAllowMismatch)
+            throwHostKeyException();
+        break;
+    case SshHostKeyDatabase::KeyLookupNoMatch:
+        if (m_connParams.hostKeyCheckingMode == SshHostKeyCheckingStrict)
+            throwHostKeyException();
+        break;
+    }
+    m_connParams.hostKeyDatabase->insertHostKey(m_connParams.host(), hostKey);
+}
+
+void SshKeyExchange::throwHostKeyException()
+{
+    throw SshServerException(SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, "Host key changed",
+                             SSH_TR("Host key of machine \"%1\" has changed.")
+                             .arg(m_connParams.host()));
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 105 - 0
src/tool/serverctrl/qssh/sshkeyexchange_p.h

@@ -0,0 +1,105 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHKEYEXCHANGE_P_H
+#define SSHKEYEXCHANGE_P_H
+
+#include "sshconnection.h"
+
+#include <QByteArray>
+#include <QScopedPointer>
+
+#include <memory>
+
+namespace Botan {
+class DH_PrivateKey;
+class ECDH_PrivateKey;
+class HashFunction;
+}
+
+namespace QSsh {
+namespace Internal {
+
+struct SshKeyExchangeInit;
+class SshSendFacility;
+class SshIncomingPacket;
+
+class SshKeyExchange
+{
+public:
+    SshKeyExchange(const SshConnectionParameters &connParams, SshSendFacility &sendFacility);
+    ~SshKeyExchange();
+
+    const QByteArray &hostKeyFingerprint() { return m_hostFingerprint; }
+    void sendKexInitPacket(const QByteArray &serverId);
+
+    // Returns true <=> the server sends a guessed package.
+    bool sendDhInitPacket(const SshIncomingPacket &serverKexInit);
+
+    void sendNewKeysPacket(const SshIncomingPacket &dhReply,
+        const QByteArray &clientId);
+
+    QByteArray k() const { return m_k; }
+    QByteArray h() const { return m_h; }
+    Botan::HashFunction *hash() const { return m_hash.get(); }
+    QByteArray encryptionAlgo() const { return m_encryptionAlgo; }
+    QByteArray decryptionAlgo() const { return m_decryptionAlgo; }
+    QByteArray hMacAlgoClientToServer() const { return m_c2sHMacAlgo; }
+    QByteArray hMacAlgoServerToClient() const { return m_s2cHMacAlgo; }
+
+private:
+    QByteArray hashAlgoForKexAlgo() const;
+    void determineHashingAlgorithm(const SshKeyExchangeInit &kexInit, bool serverToClient);
+    void checkHostKey(const QByteArray &hostKey);
+    Q_NORETURN void throwHostKeyException();
+
+    QByteArray m_serverId;
+    QByteArray m_clientKexInitPayload;
+    QByteArray m_serverKexInitPayload;
+    QScopedPointer<Botan::DH_PrivateKey> m_dhKey;
+    QScopedPointer<Botan::ECDH_PrivateKey> m_ecdhKey;
+    QByteArray m_kexAlgoName;
+    QByteArray m_k;
+    QByteArray m_h;
+    QByteArray m_serverHostKeyAlgo;
+    QByteArray m_encryptionAlgo;
+    QByteArray m_decryptionAlgo;
+    QByteArray m_c2sHMacAlgo;
+    QByteArray m_s2cHMacAlgo;
+    std::unique_ptr<Botan::HashFunction> m_hash;
+    const SshConnectionParameters m_connParams;
+    SshSendFacility &m_sendFacility;
+    QByteArray m_hostFingerprint;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHKEYEXCHANGE_P_H

+ 245 - 0
src/tool/serverctrl/qssh/sshkeygenerator.cpp

@@ -0,0 +1,245 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshkeygenerator.h"
+
+#include "sshbotanconversions_p.h"
+#include "sshcapabilities_p.h"
+#include "ssh_global.h"
+#include "sshpacket_p.h"
+#include "sshlogging_p.h"
+
+#include <botan/rsa.h>
+#include <botan/dsa.h>
+#include <botan/auto_rng.h>
+#include <botan/pipe.h>
+#include <botan/pkcs8.h>
+#include <botan/der_enc.h>
+#include <botan/pem.h>
+#include <botan/x509cert.h>
+#include <botan/x509_key.h>
+#include <botan/numthry.h>
+#include <botan/ecdsa.h>
+
+#include <QDateTime>
+#include <QInputDialog>
+
+#include <string>
+
+namespace QSsh {
+
+using namespace Botan;
+using namespace Internal;
+
+SshKeyGenerator::SshKeyGenerator() : m_type(Rsa)
+{
+}
+
+bool SshKeyGenerator::generateKeys(KeyType type, PrivateKeyFormat format, int keySize,
+    EncryptionMode encryptionMode)
+{
+    m_type = type;
+    m_encryptionMode = encryptionMode;
+
+    try {
+        AutoSeeded_RNG rng;
+        KeyPtr key;
+        switch (m_type) {
+        case Rsa:
+            key = KeyPtr(new RSA_PrivateKey(rng, keySize));
+            break;
+        case Dsa:
+            key = KeyPtr(new DSA_PrivateKey(rng, DL_Group(rng, DL_Group::DSA_Kosherizer, keySize)));
+            break;
+        case Ecdsa: {
+            const QByteArray algo = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(keySize / 8);
+            key = KeyPtr(new ECDSA_PrivateKey(rng, EC_Group(SshCapabilities::oid(algo))));
+            break;
+        }
+        }
+        switch (format) {
+        case Pkcs8:
+            generatePkcs8KeyStrings(key, rng);
+            break;
+        case OpenSsl:
+            generateOpenSslKeyStrings(key);
+            break;
+        case Mixed:
+        default:
+            generatePkcs8KeyString(key, true, rng);
+            generateOpenSslPublicKeyString(key);
+        }
+        return true;
+    } catch (const std::exception &e) {
+        m_error = tr("Error generating key: %1").arg(QString::fromLocal8Bit(e.what()));
+        return false;
+    }
+}
+
+void SshKeyGenerator::generatePkcs8KeyStrings(const KeyPtr &key, RandomNumberGenerator &rng)
+{
+    generatePkcs8KeyString(key, false, rng);
+    generatePkcs8KeyString(key, true, rng);
+}
+
+void SshKeyGenerator::generatePkcs8KeyString(const KeyPtr &key, bool privateKey,
+    RandomNumberGenerator &rng)
+{
+    Pipe pipe;
+    pipe.start_msg();
+    QByteArray *keyData;
+    if (privateKey) {
+        QString password;
+        if (m_encryptionMode == DoOfferEncryption)
+            password = getPassword();
+        if (!password.isEmpty())
+            pipe.write(PKCS8::PEM_encode(*key, rng, password.toLocal8Bit().data()));
+        else
+            pipe.write(PKCS8::PEM_encode(*key));
+        keyData = &m_privateKey;
+    } else {
+        pipe.write(X509::PEM_encode(*key));
+        keyData = &m_publicKey;
+    }
+    pipe.end_msg();
+    keyData->resize(static_cast<int>(pipe.remaining(pipe.message_count() - 1)));
+    size_t readSize = pipe.read(convertByteArray(*keyData), keyData->size(),
+        pipe.message_count() - 1);
+    if (readSize != size_t(keyData->size())) {
+        qCWarning(sshLog, "Didn't manage to read in all key data, only read %lu bytes", readSize);
+    }
+}
+
+void SshKeyGenerator::generateOpenSslKeyStrings(const KeyPtr &key)
+{
+    generateOpenSslPublicKeyString(key);
+    generateOpenSslPrivateKeyString(key);
+}
+
+void SshKeyGenerator::generateOpenSslPublicKeyString(const KeyPtr &key)
+{
+    QList<BigInt> params;
+    QByteArray keyId;
+    QByteArray q;
+    switch (m_type) {
+    case Rsa: {
+        const QSharedPointer<RSA_PrivateKey> rsaKey = key.dynamicCast<RSA_PrivateKey>();
+        params << rsaKey->get_e() << rsaKey->get_n();
+        keyId = SshCapabilities::PubKeyRsa;
+        break;
+    }
+    case Dsa: {
+        const QSharedPointer<DSA_PrivateKey> dsaKey = key.dynamicCast<DSA_PrivateKey>();
+        params << dsaKey->group_p() << dsaKey->group_q() << dsaKey->group_g() << dsaKey->get_y();
+        keyId = SshCapabilities::PubKeyDss;
+        break;
+    }
+    case Ecdsa: {
+        const auto ecdsaKey = key.dynamicCast<ECDSA_PrivateKey>();
+        q = convertByteArray(ecdsaKey->public_point().encode(PointGFp::UNCOMPRESSED));
+        keyId = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(
+                    static_cast<int>(ecdsaKey->private_value().bytes()));
+        break;
+    }
+    }
+
+    QByteArray publicKeyBlob = AbstractSshPacket::encodeString(keyId);
+    for (const BigInt &b : params) {
+        publicKeyBlob += AbstractSshPacket::encodeMpInt(b);
+    }
+    if (!q.isEmpty()) {
+        publicKeyBlob += AbstractSshPacket::encodeString(keyId.mid(11)); // Without "ecdsa-sha2-" prefix.
+        publicKeyBlob += AbstractSshPacket::encodeString(q);
+    }
+    publicKeyBlob = publicKeyBlob.toBase64();
+    const QByteArray id = "QtCreator/"
+        + QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8();
+    m_publicKey = keyId + ' ' + publicKeyBlob + ' ' + id;
+}
+
+void SshKeyGenerator::generateOpenSslPrivateKeyString(const KeyPtr &key)
+{
+    QList<BigInt> params;
+    const char *label = "";
+    switch (m_type) {
+    case Rsa: {
+        const QSharedPointer<RSA_PrivateKey> rsaKey
+            = key.dynamicCast<RSA_PrivateKey>();
+        params << rsaKey->get_n() << rsaKey->get_e() << rsaKey->get_d() << rsaKey->get_p()
+            << rsaKey->get_q();
+        const BigInt dmp1 = rsaKey->get_d() % (rsaKey->get_p() - 1);
+        const BigInt dmq1 = rsaKey->get_d() % (rsaKey->get_q() - 1);
+        const BigInt iqmp = inverse_mod(rsaKey->get_q(), rsaKey->get_p());
+        params << dmp1 << dmq1 << iqmp;
+        label = "RSA PRIVATE KEY";
+        break;
+    }
+    case Dsa: {
+        const QSharedPointer<DSA_PrivateKey> dsaKey = key.dynamicCast<DSA_PrivateKey>();
+        params << dsaKey->group_p() << dsaKey->group_q() << dsaKey->group_g() << dsaKey->get_y()
+            << dsaKey->get_x();
+        label = "DSA PRIVATE KEY";
+        break;
+    }
+    case Ecdsa:
+        params << key.dynamicCast<ECDSA_PrivateKey>()->private_value();
+        label = "EC PRIVATE KEY";
+        break;
+    }
+
+    DER_Encoder encoder;
+    encoder.start_cons(SEQUENCE).encode(size_t(0));
+    for (const BigInt &b : params) {
+        encoder.encode(b);
+    }
+    encoder.end_cons();
+    m_privateKey = QByteArray(PEM_Code::encode (encoder.get_contents(), label).c_str());
+}
+
+QString SshKeyGenerator::getPassword() const
+{
+    QInputDialog d;
+    d.setInputMode(QInputDialog::TextInput);
+    d.setTextEchoMode(QLineEdit::Password);
+    d.setWindowTitle(tr("Password for Private Key"));
+    d.setLabelText(tr("It is recommended that you secure your private key\n"
+        "with a password, which you can enter below."));
+    d.setOkButtonText(tr("Encrypt Key File"));
+    d.setCancelButtonText(tr("Do Not Encrypt Key File"));
+    int result = QDialog::Accepted;
+    QString password;
+    while (result == QDialog::Accepted && password.isEmpty()) {
+        result = d.exec();
+        password = d.textValue();
+    }
+    return result == QDialog::Accepted ? password : QString();
+}
+
+} // namespace QSsh

+ 83 - 0
src/tool/serverctrl/qssh/sshkeygenerator.h

@@ -0,0 +1,83 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHKEYGENERATOR_H
+#define SSHKEYGENERATOR_H
+
+#include "ssh_global.h"
+
+#include <QCoreApplication>
+#include <QSharedPointer>
+
+namespace Botan {
+    class Private_Key;
+    class RandomNumberGenerator;
+}
+
+namespace QSsh {
+
+class QSSH_EXPORT SshKeyGenerator
+{
+    Q_DECLARE_TR_FUNCTIONS(SshKeyGenerator)
+public:
+    enum KeyType { Rsa, Dsa, Ecdsa };
+    enum PrivateKeyFormat { Pkcs8, OpenSsl, Mixed };
+    enum EncryptionMode { DoOfferEncryption, DoNotOfferEncryption }; // Only relevant for Pkcs8 format.
+
+    SshKeyGenerator();
+    bool generateKeys(KeyType type, PrivateKeyFormat format, int keySize,
+        EncryptionMode encryptionMode = DoOfferEncryption);
+
+    QString error() const { return m_error; }
+    QByteArray privateKey() const { return m_privateKey; }
+    QByteArray publicKey() const { return m_publicKey; }
+    KeyType type() const { return m_type; }
+
+private:
+    typedef QSharedPointer<Botan::Private_Key> KeyPtr;
+
+    void generatePkcs8KeyStrings(const KeyPtr &key, Botan::RandomNumberGenerator &rng);
+    void generatePkcs8KeyString(const KeyPtr &key, bool privateKey,
+        Botan::RandomNumberGenerator &rng);
+    void generateOpenSslKeyStrings(const KeyPtr &key);
+    void generateOpenSslPrivateKeyString(const KeyPtr &key);
+    void generateOpenSslPublicKeyString(const KeyPtr &key);
+    QString getPassword() const;
+
+    QString m_error;
+    QByteArray m_publicKey;
+    QByteArray m_privateKey;
+    KeyType m_type;
+    EncryptionMode m_encryptionMode;
+};
+
+} // namespace QSsh
+
+#endif // SSHKEYGENERATOR_H

+ 60 - 0
src/tool/serverctrl/qssh/sshkeypasswordretriever.cpp

@@ -0,0 +1,60 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+#include "sshkeypasswordretriever_p.h"
+
+#include <QString>
+#include <QApplication>
+#include <QInputDialog>
+
+#include <iostream>
+
+namespace QSsh {
+namespace Internal {
+
+std::string SshKeyPasswordRetriever::get_passphrase()
+{
+    const bool hasGui = dynamic_cast<QApplication *>(QApplication::instance());
+    if (hasGui) {
+        bool ok;
+        const QString &password = QInputDialog::getText(nullptr,
+            QCoreApplication::translate("QSsh::Ssh", "Password Required"),
+            QCoreApplication::translate("QSsh::Ssh", "Please enter the password for your private key."),
+            QLineEdit::Password, QString(), &ok);
+        return std::string(password.toLocal8Bit().data());
+    } else {
+        std::string password;
+        std::cout << "Please enter the password for your private key (set echo off beforehand!): " << std::flush;
+        std::cin >> password;
+        return password;
+    }
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 47 - 0
src/tool/serverctrl/qssh/sshkeypasswordretriever_p.h

@@ -0,0 +1,47 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+#ifndef KEYPASSWORDRETRIEVER_H
+#define KEYPASSWORDRETRIEVER_H
+
+#include <string>
+
+namespace QSsh {
+namespace Internal {
+
+class SshKeyPasswordRetriever
+{
+public:
+    static std::string get_passphrase();
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // KEYPASSWORDRETRIEVER_H

+ 37 - 0
src/tool/serverctrl/qssh/sshlogging.cpp

@@ -0,0 +1,37 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company.  For licensing terms and
+** conditions see http://www.qt.io/terms-conditions.  For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights.  These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include "sshlogging_p.h"
+
+namespace QSsh {
+namespace Internal {
+Q_LOGGING_CATEGORY(sshLog, "qtc.ssh", QtWarningMsg)
+} // namespace Internal
+} // namespace QSsh

+ 42 - 0
src/tool/serverctrl/qssh/sshlogging_p.h

@@ -0,0 +1,42 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company.  For licensing terms and
+** conditions see http://www.qt.io/terms-conditions.  For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights.  These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#ifndef SSHLOGGING_P_H
+#define SSHLOGGING_P_H
+
+#include <QLoggingCategory>
+
+namespace QSsh {
+namespace Internal {
+Q_DECLARE_LOGGING_CATEGORY(sshLog)
+} // namespace Internal
+} // namespace QSsh
+
+#endif // Include guard

+ 421 - 0
src/tool/serverctrl/qssh/sshoutgoingpacket.cpp

@@ -0,0 +1,421 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshoutgoingpacket_p.h"
+
+#include "sshagent_p.h"
+#include "sshcapabilities_p.h"
+#include "sshcryptofacility_p.h"
+#include "sshlogging_p.h"
+#include "sshpacketparser_p.h"
+
+#include <QtEndian>
+
+namespace QSsh {
+namespace Internal {
+
+SshOutgoingPacket::SshOutgoingPacket(const SshEncryptionFacility &encrypter,
+    const quint32 &seqNr) : m_encrypter(encrypter), m_seqNr(seqNr)
+{
+}
+
+quint32 SshOutgoingPacket::cipherBlockSize() const
+{
+    return qMax(m_encrypter.cipherBlockSize(), 4U);
+}
+
+quint32 SshOutgoingPacket::macLength() const
+{
+    return m_encrypter.macLength();
+}
+
+QByteArray SshOutgoingPacket::generateKeyExchangeInitPacket()
+{
+    const QByteArray &supportedkeyExchangeMethods
+        = encodeNameList(SshCapabilities::KeyExchangeMethods);
+    const QByteArray &supportedPublicKeyAlgorithms
+        = encodeNameList(SshCapabilities::PublicKeyAlgorithms);
+    const QByteArray &supportedEncryptionAlgorithms
+        = encodeNameList(SshCapabilities::EncryptionAlgorithms);
+    const QByteArray &supportedMacAlgorithms
+        = encodeNameList(SshCapabilities::MacAlgorithms);
+    const QByteArray &supportedCompressionAlgorithms
+        = encodeNameList(SshCapabilities::CompressionAlgorithms);
+    const QByteArray &supportedLanguages = encodeNameList(QList<QByteArray>());
+
+    init(SSH_MSG_KEXINIT);
+    m_data += m_encrypter.getRandomNumbers(16);
+    m_data.append(supportedkeyExchangeMethods);
+    m_data.append(supportedPublicKeyAlgorithms);
+    m_data.append(supportedEncryptionAlgorithms)
+        .append(supportedEncryptionAlgorithms);
+    m_data.append(supportedMacAlgorithms).append(supportedMacAlgorithms);
+    m_data.append(supportedCompressionAlgorithms)
+        .append(supportedCompressionAlgorithms);
+    m_data.append(supportedLanguages).append(supportedLanguages);
+    appendBool(false); // No guessed packet.
+    m_data.append(QByteArray(4, 0)); // Reserved.
+    QByteArray payload = m_data.mid(PayloadOffset);
+    finalize();
+    return payload;
+}
+
+void SshOutgoingPacket::generateKeyDhInitPacket(const Botan::BigInt &e)
+{
+    init(SSH_MSG_KEXDH_INIT).appendMpInt(e).finalize();
+}
+
+void SshOutgoingPacket::generateKeyEcdhInitPacket(const QByteArray &clientQ)
+{
+    init(SSH_MSG_KEX_ECDH_INIT).appendString(clientQ).finalize();
+}
+
+void SshOutgoingPacket::generateNewKeysPacket()
+{
+    init(SSH_MSG_NEWKEYS).finalize();
+}
+
+void SshOutgoingPacket::generateUserAuthServiceRequestPacket()
+{
+    generateServiceRequest("ssh-userauth");
+}
+
+void SshOutgoingPacket::generateServiceRequest(const QByteArray &service)
+{
+    init(SSH_MSG_SERVICE_REQUEST).appendString(service).finalize();
+}
+
+void SshOutgoingPacket::generateUserAuthByPasswordRequestPacket(const QByteArray &user,
+    const QByteArray &service, const QByteArray &pwd)
+{
+    init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service);
+    if (pwd.isEmpty())
+        appendString("none"); // RFC 4252, 5.2
+    else
+        appendString("password").appendBool(false).appendString(pwd);
+    finalize();
+}
+
+void SshOutgoingPacket::generateUserAuthByPublicKeyRequestPacket(const QByteArray &user,
+    const QByteArray &service, const QByteArray &key, const QByteArray &signature)
+{
+    init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service)
+        .appendString("publickey").appendBool(true);
+    if (!key.isEmpty()) {
+        appendString(SshPacketParser::asString(key, quint32(0)));
+        appendString(key);
+        appendString(signature);
+    } else {
+        appendString(m_encrypter.authenticationAlgorithmName());
+        appendString(m_encrypter.authenticationPublicKey());
+        const QByteArray &dataToSign = m_data.mid(PayloadOffset);
+        appendString(m_encrypter.authenticationKeySignature(dataToSign));
+    }
+    finalize();
+}
+
+void SshOutgoingPacket::generateQueryPublicKeyPacket(const QByteArray &user,
+        const QByteArray &service, const QByteArray &publicKey)
+{
+    // Name extraction cannot fail, we already verified this when receiving the key
+    // from the agent.
+    const QByteArray algoName = SshPacketParser::asString(publicKey, quint32(0));
+    SshOutgoingPacket packetToSign(m_encrypter, m_seqNr);
+    packetToSign.init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service)
+            .appendString("publickey").appendBool(true).appendString(algoName)
+            .appendString(publicKey);
+    const QByteArray &dataToSign
+            = encodeString(m_encrypter.sessionId()) + packetToSign.m_data.mid(PayloadOffset);
+    SshAgent::storeDataToSign(publicKey, dataToSign, qHash(m_encrypter.sessionId()));
+    init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service)
+            .appendString("publickey").appendBool(false).appendString(algoName)
+            .appendString(publicKey).finalize();
+}
+
+void SshOutgoingPacket::generateUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
+                                                                           const QByteArray &service)
+{
+    // RFC 4256, 3.1
+    init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service)
+            .appendString("keyboard-interactive")
+            .appendString(QByteArray()) // Language tag. Deprecated and should be empty
+            .appendString(QByteArray()) // Submethods.
+            .finalize();
+}
+
+void SshOutgoingPacket::generateUserAuthInfoResponsePacket(const QStringList &responses)
+{
+    // RFC 4256, 3.4
+    init(SSH_MSG_USERAUTH_INFO_RESPONSE).appendInt(responses.count());
+    for (const QString &response : responses) {
+        appendString(response.toUtf8());
+    }
+    finalize();
+}
+
+void SshOutgoingPacket::generateRequestFailurePacket()
+{
+    init(SSH_MSG_REQUEST_FAILURE).finalize();
+}
+
+void SshOutgoingPacket::generateIgnorePacket()
+{
+    init(SSH_MSG_IGNORE).finalize();
+}
+
+void SshOutgoingPacket::generateInvalidMessagePacket()
+{
+    init(SSH_MSG_INVALID).finalize();
+}
+
+void SshOutgoingPacket::generateSessionPacket(quint32 channelId,
+    quint32 windowSize, quint32 maxPacketSize)
+{
+    init(SSH_MSG_CHANNEL_OPEN).appendString("session").appendInt(channelId)
+            .appendInt(windowSize).appendInt(maxPacketSize).finalize();
+}
+
+void SshOutgoingPacket::generateDirectTcpIpPacket(quint32 channelId, quint32 windowSize,
+        quint32 maxPacketSize, const QByteArray &remoteHost, quint32 remotePort,
+        const QByteArray &localIpAddress, quint32 localPort)
+{
+    init(SSH_MSG_CHANNEL_OPEN).appendString("direct-tcpip").appendInt(channelId)
+            .appendInt(windowSize).appendInt(maxPacketSize).appendString(remoteHost)
+            .appendInt(remotePort).appendString(localIpAddress).appendInt(localPort).finalize();
+}
+
+void SshOutgoingPacket::generateTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort)
+{
+    init(SSH_MSG_GLOBAL_REQUEST).appendString("tcpip-forward").appendBool(true)
+            .appendString(bindAddress).appendInt(bindPort).finalize();
+}
+
+void SshOutgoingPacket::generateCancelTcpIpForwardPacket(const QByteArray &bindAddress,
+                                                         quint32 bindPort)
+{
+    init(SSH_MSG_GLOBAL_REQUEST).appendString("cancel-tcpip-forward").appendBool(true)
+            .appendString(bindAddress).appendInt(bindPort).finalize();
+}
+
+void SshOutgoingPacket::generateEnvPacket(quint32 remoteChannel,
+    const QByteArray &var, const QByteArray &value)
+{
+    init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("env")
+        .appendBool(false).appendString(var).appendString(value).finalize();
+}
+
+void SshOutgoingPacket::generateX11ForwardingPacket(quint32 remoteChannel,
+        const QByteArray &protocol, const QByteArray &cookie, quint32 screenNumber)
+{
+    init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("x11-req")
+            .appendBool(false).appendBool(false).appendString(protocol)
+            .appendString(cookie).appendInt(screenNumber).finalize();
+}
+
+void SshOutgoingPacket::generatePtyRequestPacket(quint32 remoteChannel,
+    const SshPseudoTerminal &terminal)
+{
+    init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel)
+        .appendString("pty-req").appendBool(false)
+        .appendString(terminal.termType).appendInt(terminal.columnCount)
+        .appendInt(terminal.rowCount).appendInt(0).appendInt(0);
+    QByteArray modeString;
+    for (SshPseudoTerminal::ModeMap::ConstIterator it = terminal.modes.constBegin();
+         it != terminal.modes.constEnd(); ++it) {
+        modeString += char(it.key());
+        modeString += encodeInt(it.value());
+    }
+    modeString += char(0); // TTY_OP_END
+    appendString(modeString).finalize();
+}
+
+void SshOutgoingPacket::generateExecPacket(quint32 remoteChannel,
+    const QByteArray &command)
+{
+    init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("exec")
+        .appendBool(true).appendString(command).finalize();
+}
+
+void SshOutgoingPacket::generateShellPacket(quint32 remoteChannel)
+{
+    init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("shell")
+        .appendBool(true).finalize();
+}
+
+void SshOutgoingPacket::generateSftpPacket(quint32 remoteChannel)
+{
+    init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel)
+        .appendString("subsystem").appendBool(true).appendString("sftp")
+        .finalize();
+}
+
+void SshOutgoingPacket::generateWindowAdjustPacket(quint32 remoteChannel,
+    quint32 bytesToAdd)
+{
+    init(SSH_MSG_CHANNEL_WINDOW_ADJUST).appendInt(remoteChannel)
+        .appendInt(bytesToAdd).finalize();
+}
+
+void SshOutgoingPacket::generateChannelDataPacket(quint32 remoteChannel,
+    const QByteArray &data)
+{
+    init(SSH_MSG_CHANNEL_DATA).appendInt(remoteChannel).appendString(data)
+        .finalize();
+}
+
+void SshOutgoingPacket::generateChannelSignalPacket(quint32 remoteChannel,
+    const QByteArray &signalName)
+{
+    init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel)
+        .appendString("signal").appendBool(false).appendString(signalName)
+        .finalize();
+}
+
+void SshOutgoingPacket::generateChannelEofPacket(quint32 remoteChannel)
+{
+    init(SSH_MSG_CHANNEL_EOF).appendInt(remoteChannel).finalize();
+}
+
+void SshOutgoingPacket::generateChannelClosePacket(quint32 remoteChannel)
+{
+    init(SSH_MSG_CHANNEL_CLOSE).appendInt(remoteChannel).finalize();
+}
+
+void SshOutgoingPacket::generateChannelOpenConfirmationPacket(quint32 remoteChannel,
+                                                              quint32 localChannel,
+                                                              quint32 localWindowSize,
+                                                              quint32 maxPacketSize)
+{
+    init(SSH_MSG_CHANNEL_OPEN_CONFIRMATION).appendInt(remoteChannel).appendInt(localChannel)
+            .appendInt(localWindowSize).appendInt(maxPacketSize).finalize();
+}
+
+void SshOutgoingPacket::generateChannelOpenFailurePacket(quint32 remoteChannel, quint32 reason,
+                                                         const QByteArray &reasonString)
+{
+    init(SSH_MSG_CHANNEL_OPEN_FAILURE).appendInt(remoteChannel).appendInt(reason)
+            .appendString(reasonString).appendString(QByteArray()).finalize();
+}
+
+void SshOutgoingPacket::generateDisconnectPacket(SshErrorCode reason,
+    const QByteArray &reasonString)
+{
+    init(SSH_MSG_DISCONNECT).appendInt(reason).appendString(reasonString)
+        .appendString(QByteArray()).finalize();
+}
+
+void SshOutgoingPacket::generateMsgUnimplementedPacket(quint32 serverSeqNr)
+{
+    init(SSH_MSG_UNIMPLEMENTED).appendInt(serverSeqNr).finalize();
+}
+
+SshOutgoingPacket &SshOutgoingPacket::appendInt(quint32 val)
+{
+    m_data.append(encodeInt(val));
+    return *this;
+}
+
+SshOutgoingPacket &SshOutgoingPacket::appendMpInt(const Botan::BigInt &number)
+{
+    m_data.append(encodeMpInt(number));
+    return *this;
+}
+
+SshOutgoingPacket &SshOutgoingPacket::appendBool(bool b)
+{
+    m_data += static_cast<char>(b);
+    return *this;
+}
+
+SshOutgoingPacket &SshOutgoingPacket::appendString(const QByteArray &string)
+{
+    m_data.append(encodeString(string));
+    return *this;
+}
+
+SshOutgoingPacket &SshOutgoingPacket::init(SshPacketType type)
+{
+    m_data.resize(TypeOffset + 1);
+    m_data[TypeOffset] = type;
+    return *this;
+}
+
+SshOutgoingPacket &SshOutgoingPacket::setPadding()
+{
+    m_data += m_encrypter.getRandomNumbers(MinPaddingLength);
+    int padLength = MinPaddingLength;
+    const int divisor = sizeDivisor();
+    const int mod = m_data.size() % divisor;
+    padLength += divisor - mod;
+    m_data += m_encrypter.getRandomNumbers(padLength - MinPaddingLength);
+    m_data[PaddingLengthOffset] = padLength;
+    return *this;
+}
+
+SshOutgoingPacket &SshOutgoingPacket::encrypt()
+{
+    const QByteArray &mac
+        = generateMac(m_encrypter, m_seqNr);
+    m_encrypter.encrypt(m_data);
+    m_data += mac;
+    return *this;
+}
+
+void SshOutgoingPacket::finalize()
+{
+    setPadding();
+    setLengthField(m_data);
+    m_length = m_data.size() - 4;
+    qCDebug(sshLog) << "Encrypting packet of type" << int(m_data.at(TypeOffset));
+    encrypt();
+    qCDebug(sshLog) << "Sending packet of size" << rawData().count();
+    Q_ASSERT(isComplete());
+}
+
+int SshOutgoingPacket::sizeDivisor() const
+{
+    return qMax(cipherBlockSize(), 8U);
+}
+
+QByteArray SshOutgoingPacket::encodeNameList(const QList<QByteArray> &list)
+{
+    QByteArray data;
+    data.resize(4);
+    for (int i = 0; i < list.count(); ++i) {
+        if (i > 0)
+            data.append(',');
+        data.append(list.at(i));
+    }
+    AbstractSshPacket::setLengthField(data);
+    return data;
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 125 - 0
src/tool/serverctrl/qssh/sshoutgoingpacket_p.h

@@ -0,0 +1,125 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHOUTGOINGPACKET_P_H
+#define SSHOUTGOINGPACKET_P_H
+
+#include "sshpacket_p.h"
+
+#include "sshpseudoterminal.h"
+
+#include <QStringList>
+
+namespace QSsh {
+namespace Internal {
+
+class SshEncryptionFacility;
+
+class SshOutgoingPacket : public AbstractSshPacket
+{
+public:
+    SshOutgoingPacket(const SshEncryptionFacility &encrypter,
+        const quint32 &seqNr);
+
+    QByteArray generateKeyExchangeInitPacket(); // Returns payload.
+    void generateKeyDhInitPacket(const Botan::BigInt &e);
+    void generateKeyEcdhInitPacket(const QByteArray &clientQ);
+    void generateNewKeysPacket();
+    void generateDisconnectPacket(SshErrorCode reason,
+        const QByteArray &reasonString);
+    void generateMsgUnimplementedPacket(quint32 serverSeqNr);
+    void generateUserAuthServiceRequestPacket();
+    void generateUserAuthByPasswordRequestPacket(const QByteArray &user,
+        const QByteArray &service, const QByteArray &pwd);
+    void generateUserAuthByPublicKeyRequestPacket(const QByteArray &user,
+        const QByteArray &service, const QByteArray &key, const QByteArray &signature);
+    void generateQueryPublicKeyPacket(const QByteArray &user, const QByteArray &service,
+                                      const QByteArray &publicKey);
+    void generateUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
+        const QByteArray &service);
+    void generateUserAuthInfoResponsePacket(const QStringList &responses);
+    void generateRequestFailurePacket();
+    void generateIgnorePacket();
+    void generateInvalidMessagePacket();
+    void generateSessionPacket(quint32 channelId, quint32 windowSize,
+        quint32 maxPacketSize);
+    void generateDirectTcpIpPacket(quint32 channelId, quint32 windowSize,
+        quint32 maxPacketSize, const QByteArray &remoteHost, quint32 remotePort,
+        const QByteArray &localIpAddress, quint32 localPort);
+    void generateTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort);
+    void generateCancelTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort);
+    void generateEnvPacket(quint32 remoteChannel, const QByteArray &var,
+        const QByteArray &value);
+    void generateX11ForwardingPacket(quint32 remoteChannel, const QByteArray &protocol,
+                                     const QByteArray &cookie, quint32 screenNumber);
+    void generatePtyRequestPacket(quint32 remoteChannel,
+        const SshPseudoTerminal &terminal);
+    void generateExecPacket(quint32 remoteChannel, const QByteArray &command);
+    void generateShellPacket(quint32 remoteChannel);
+    void generateSftpPacket(quint32 remoteChannel);
+    void generateWindowAdjustPacket(quint32 remoteChannel, quint32 bytesToAdd);
+    void generateChannelDataPacket(quint32 remoteChannel,
+        const QByteArray &data);
+    void generateChannelSignalPacket(quint32 remoteChannel,
+        const QByteArray &signalName);
+    void generateChannelEofPacket(quint32 remoteChannel);
+    void generateChannelClosePacket(quint32 remoteChannel);
+    void generateChannelOpenConfirmationPacket(quint32 remoteChannel, quint32 localChannel,
+        quint32 localWindowSize, quint32 maxPackeSize);
+    void generateChannelOpenFailurePacket(quint32 remoteChannel, quint32 reason,
+        const QByteArray &reasonString);
+
+private:
+    virtual quint32 cipherBlockSize() const;
+    virtual quint32 macLength() const;
+
+    static QByteArray encodeNameList(const QList<QByteArray> &list);
+
+    void generateServiceRequest(const QByteArray &service);
+
+    SshOutgoingPacket &init(SshPacketType type);
+    SshOutgoingPacket &setPadding();
+    SshOutgoingPacket &encrypt();
+    void finalize();
+
+    SshOutgoingPacket &appendInt(quint32 val);
+    SshOutgoingPacket &appendString(const QByteArray &string);
+    SshOutgoingPacket &appendMpInt(const Botan::BigInt &number);
+    SshOutgoingPacket &appendBool(bool b);
+    int sizeDivisor() const;
+
+    const SshEncryptionFacility &m_encrypter;
+    const quint32 &m_seqNr;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHOUTGOINGPACKET_P_H

+ 163 - 0
src/tool/serverctrl/qssh/sshpacket.cpp

@@ -0,0 +1,163 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshpacket_p.h"
+
+#include "sshcapabilities_p.h"
+#include "sshcryptofacility_p.h"
+#include "sshexception_p.h"
+#include "sshlogging_p.h"
+#include "sshpacketparser_p.h"
+
+#include <QDebug>
+
+#include <cctype>
+
+namespace QSsh {
+namespace Internal {
+
+const quint32 AbstractSshPacket::PaddingLengthOffset = 4;
+const quint32 AbstractSshPacket::PayloadOffset = PaddingLengthOffset + 1;
+const quint32 AbstractSshPacket::TypeOffset = PayloadOffset;
+const quint32 AbstractSshPacket::MinPaddingLength = 4;
+
+static void printByteArray(const QByteArray &data)
+{
+    qCDebug(sshLog, "%s", data.toHex().constData());
+}
+
+
+AbstractSshPacket::AbstractSshPacket() : m_length(0) { }
+AbstractSshPacket::~AbstractSshPacket() {}
+
+bool AbstractSshPacket::isComplete() const
+{
+    if (currentDataSize() < minPacketSize())
+        return false;
+    return 4 + length() + macLength() == currentDataSize();
+}
+
+void AbstractSshPacket::clear()
+{
+    m_data.clear();
+    m_length = 0;
+}
+
+SshPacketType AbstractSshPacket::type() const
+{
+    Q_ASSERT(isComplete());
+    return static_cast<SshPacketType>(m_data.at(TypeOffset));
+}
+
+QByteArray AbstractSshPacket::payLoad() const
+{
+    return QByteArray(m_data.constData() + PayloadOffset,
+        length() - paddingLength() - 1);
+}
+
+void AbstractSshPacket::printRawBytes() const
+{
+    printByteArray(m_data);
+}
+
+QByteArray AbstractSshPacket::encodeString(const QByteArray &string)
+{
+    QByteArray data;
+    data.resize(4);
+    data += string;
+    setLengthField(data);
+    return data;
+}
+
+QByteArray AbstractSshPacket::encodeMpInt(const Botan::BigInt &number)
+{
+    if (number.is_zero())
+        return QByteArray(4, 0);
+
+    int stringLength = static_cast<int>(number.bytes());
+    const bool positiveAndMsbSet = number.sign() == Botan::BigInt::Positive
+                                   && (number.byte_at(stringLength - 1) & 0x80);
+    if (positiveAndMsbSet)
+        ++stringLength;
+    QByteArray data;
+    data.resize(4 + stringLength);
+    int pos = 4;
+    if (positiveAndMsbSet)
+        data[pos++] = '\0';
+    number.binary_encode(reinterpret_cast<Botan::byte *>(data.data()) + pos);
+    setLengthField(data);
+    return data;
+}
+
+int AbstractSshPacket::paddingLength() const
+{
+    return m_data[PaddingLengthOffset];
+}
+
+quint32 AbstractSshPacket::length() const
+{
+    //Q_ASSERT(currentDataSize() >= minPacketSize());
+    if (m_length == 0)
+        calculateLength();
+    return m_length;
+}
+
+void AbstractSshPacket::calculateLength() const
+{
+    m_length = SshPacketParser::asUint32(m_data, static_cast<quint32>(0));
+}
+
+QByteArray AbstractSshPacket::generateMac(const SshAbstractCryptoFacility &crypt,
+    quint32 seqNr) const
+{
+    const quint32 seqNrBe = qToBigEndian(seqNr);
+    QByteArray data(reinterpret_cast<const char *>(&seqNrBe), int(sizeof seqNrBe));
+    data += QByteArray(m_data.constData(), length() + 4);
+    return crypt.generateMac(data, data.size());
+}
+
+quint32 AbstractSshPacket::minPacketSize() const
+{
+    return qMax<quint32>(cipherBlockSize(), 16) + macLength();
+}
+
+void AbstractSshPacket::setLengthField(QByteArray &data)
+{
+    Q_ASSERT(data.size() >= 4);
+    const quint32 length = qToBigEndian<quint32>(data.size() - 4);
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+    data.replace(qsizetype(0), qsizetype(4), reinterpret_cast<const char *>(&length), qsizetype(4));
+#else
+    data.replace(0, 4, reinterpret_cast<const char *>(&length), 4);
+#endif
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 157 - 0
src/tool/serverctrl/qssh/sshpacket_p.h

@@ -0,0 +1,157 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHPACKET_P_H
+#define SSHPACKET_P_H
+
+#include "sshexception_p.h"
+
+#include <QtEndian>
+#include <QByteArray>
+#include <QList>
+
+namespace Botan { class BigInt; }
+
+namespace QSsh {
+namespace Internal {
+
+enum SshPacketType {
+    SSH_MSG_DISCONNECT = 1,
+    SSH_MSG_IGNORE = 2,
+    SSH_MSG_UNIMPLEMENTED = 3,
+    SSH_MSG_DEBUG = 4,
+    SSH_MSG_SERVICE_REQUEST = 5,
+    SSH_MSG_SERVICE_ACCEPT = 6,
+
+    SSH_MSG_KEXINIT = 20,
+    SSH_MSG_NEWKEYS = 21,
+    SSH_MSG_KEXDH_INIT = 30,
+    SSH_MSG_KEX_ECDH_INIT = 30,
+    SSH_MSG_KEXDH_REPLY = 31,
+    SSH_MSG_KEX_ECDH_REPLY = 31,
+
+    SSH_MSG_USERAUTH_REQUEST = 50,
+    SSH_MSG_USERAUTH_FAILURE = 51,
+    SSH_MSG_USERAUTH_SUCCESS = 52,
+    SSH_MSG_USERAUTH_BANNER = 53,
+    SSH_MSG_USERAUTH_PK_OK = 60,
+    SSH_MSG_USERAUTH_PASSWD_CHANGEREQ = 60,
+    SSH_MSG_USERAUTH_INFO_REQUEST = 60,
+    SSH_MSG_USERAUTH_INFO_RESPONSE = 61,
+
+    SSH_MSG_GLOBAL_REQUEST = 80,
+    SSH_MSG_REQUEST_SUCCESS = 81,
+    SSH_MSG_REQUEST_FAILURE = 82,
+
+    // TODO: We currently take no precautions against sending these messages
+    //       during a key re-exchange, which is not allowed.
+    SSH_MSG_CHANNEL_OPEN = 90,
+    SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91,
+    SSH_MSG_CHANNEL_OPEN_FAILURE = 92,
+    SSH_MSG_CHANNEL_WINDOW_ADJUST = 93,
+    SSH_MSG_CHANNEL_DATA = 94,
+    SSH_MSG_CHANNEL_EXTENDED_DATA = 95,
+    SSH_MSG_CHANNEL_EOF = 96,
+    SSH_MSG_CHANNEL_CLOSE = 97,
+    SSH_MSG_CHANNEL_REQUEST = 98,
+    SSH_MSG_CHANNEL_SUCCESS = 99,
+    SSH_MSG_CHANNEL_FAILURE = 100,
+
+    // Not completely safe, since the server may actually understand this
+    // message type as an extension. Switch to a different value in that case
+    // (between 128 and 191).
+    SSH_MSG_INVALID = 128
+};
+
+enum SshOpenFailureType {
+    SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1,
+    SSH_OPEN_CONNECT_FAILED = 2,
+    SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3,
+    SSH_OPEN_RESOURCE_SHORTAGE = 4
+};
+
+enum SshExtendedDataType { SSH_EXTENDED_DATA_STDERR = 1 };
+
+class SshAbstractCryptoFacility;
+
+class AbstractSshPacket
+{
+    Q_DISABLE_COPY(AbstractSshPacket)
+
+public:
+    virtual ~AbstractSshPacket();
+
+    void clear();
+    bool isComplete() const;
+    SshPacketType type() const;
+
+    static QByteArray encodeString(const QByteArray &string);
+    static QByteArray encodeMpInt(const Botan::BigInt &number);
+    template<typename T> static QByteArray encodeInt(T value)
+    {
+        const T valMsb = qToBigEndian(value);
+        return QByteArray(reinterpret_cast<const char *>(&valMsb), sizeof valMsb);
+    }
+
+    static void setLengthField(QByteArray &data);
+
+    void printRawBytes() const; // For Debugging.
+
+    const QByteArray &rawData() const { return m_data; }
+
+    QByteArray payLoad() const;
+
+protected:
+    AbstractSshPacket();
+
+    virtual quint32 cipherBlockSize() const = 0;
+    virtual quint32 macLength() const = 0;
+    virtual void calculateLength() const;
+
+    quint32 length() const;
+    int paddingLength() const;
+    quint32 minPacketSize() const;
+    quint32 currentDataSize() const { return m_data.size(); }
+    QByteArray generateMac(const SshAbstractCryptoFacility &crypt,
+        quint32 seqNr) const;
+
+    static const quint32 PaddingLengthOffset;
+    static const quint32 PayloadOffset;
+    static const quint32 TypeOffset;
+    static const quint32 MinPaddingLength;
+
+    mutable QByteArray m_data;
+    mutable quint32 m_length;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHPACKET_P_H

+ 149 - 0
src/tool/serverctrl/qssh/sshpacketparser.cpp

@@ -0,0 +1,149 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshpacketparser_p.h"
+
+#include <cctype>
+#include <QtEndian>
+
+namespace QSsh {
+namespace Internal {
+
+namespace { quint32 size(const QByteArray &data) { return data.size(); } }
+
+QString SshPacketParser::asUserString(const QByteArray &rawString)
+{
+    QByteArray filteredString;
+    filteredString.resize(rawString.size());
+    for (int i = 0; i < rawString.size(); ++i) {
+        const char c = rawString.at(i);
+        filteredString[i]
+            = std::isprint(c) || c == '\n' || c == '\r' || c == '\t' ? c : '?';
+    }
+    return QString::fromUtf8(filteredString);
+}
+
+bool SshPacketParser::asBool(const QByteArray &data, quint32 offset)
+{
+    if (size(data) <= offset)
+        throw SshPacketParseException();
+    return data.at(offset);
+}
+
+bool SshPacketParser::asBool(const QByteArray &data, quint32 *offset)
+{
+    bool b = asBool(data, *offset);
+    ++(*offset);
+    return b;
+}
+
+
+quint32 SshPacketParser::asUint32(const QByteArray &data, quint32 offset)
+{
+    if (size(data) < offset + 4)
+        throw SshPacketParseException();
+    return qFromBigEndian<quint32>(data.constData() + offset);
+}
+
+quint32 SshPacketParser::asUint32(const QByteArray &data, quint32 *offset)
+{
+    const quint32 v = asUint32(data, *offset);
+    *offset += 4;
+    return v;
+}
+
+quint64 SshPacketParser::asUint64(const QByteArray &data, quint32 offset)
+{
+    if (size(data) < offset + 8)
+        throw SshPacketParseException();
+    return qFromBigEndian<quint64>(data.constData() + offset);
+}
+
+quint64 SshPacketParser::asUint64(const QByteArray &data, quint32 *offset)
+{
+    const quint64 val = asUint64(data, *offset);
+    *offset += 8;
+    return val;
+}
+
+QByteArray SshPacketParser::asString(const QByteArray &data, quint32 offset)
+{
+    return asString(data, &offset);
+}
+
+QByteArray SshPacketParser::asString(const QByteArray &data, quint32 *offset)
+{
+    const quint32 length = asUint32(data, offset);
+    if (size(data) < *offset + length)
+        throw SshPacketParseException();
+    const QByteArray &string = data.mid(*offset, length);
+    *offset += length;
+    return string;
+}
+
+QString SshPacketParser::asUserString(const QByteArray &data, quint32 *offset)
+{
+    return asUserString(asString(data, offset));
+}
+
+SshNameList SshPacketParser::asNameList(const QByteArray &data, quint32 *offset)
+{
+    const quint32 length = asUint32(data, offset);
+    const int listEndPos = *offset + length;
+    if (data.size() < listEndPos)
+        throw SshPacketParseException();
+    SshNameList names(length + 4);
+    int nextNameOffset = *offset;
+    int nextCommaOffset = data.indexOf(',', nextNameOffset);
+    while (nextNameOffset > 0 && nextNameOffset < listEndPos) {
+        const int stringEndPos = nextCommaOffset == -1
+            || nextCommaOffset > listEndPos ? listEndPos : nextCommaOffset;
+        names.names << QByteArray(data.constData() + nextNameOffset,
+            stringEndPos - nextNameOffset);
+        nextNameOffset = nextCommaOffset + 1;
+        nextCommaOffset = data.indexOf(',', nextNameOffset);
+    }
+    *offset += length;
+    return names;
+}
+
+Botan::BigInt SshPacketParser::asBigInt(const QByteArray &data, quint32 *offset)
+{
+    const quint32 length = asUint32(data, offset);
+    if (length == 0)
+        return Botan::BigInt();
+    const Botan::byte *numberStart
+        = reinterpret_cast<const Botan::byte *>(data.constData() + *offset);
+    *offset += length;
+    return Botan::BigInt::decode(numberStart, length);
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 83 - 0
src/tool/serverctrl/qssh/sshpacketparser_p.h

@@ -0,0 +1,83 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHPACKETPARSER_P_H
+#define SSHPACKETPARSER_P_H
+
+#include <botan/bigint.h>
+
+#include <QByteArray>
+#include <QList>
+#include <QString>
+
+namespace QSsh {
+namespace Internal {
+
+struct SshNameList
+{
+    SshNameList() : originalLength(0) {}
+    SshNameList(quint32 originalLength) : originalLength(originalLength) {}
+    quint32 originalLength;
+    QList<QByteArray> names;
+};
+
+class SshPacketParseException { };
+
+// This class's functions try to read a byte array at a certain offset
+// as the respective chunk of data as specified in the SSH RFCs.
+// If they succeed, they update the offset, so they can easily
+// be called in succession by client code.
+// For convenience, some have also versions that don't update the offset,
+// so they can be called with rvalues if the new value is not needed.
+// If they fail, they throw an SshPacketParseException.
+class SshPacketParser
+{
+public:
+    static bool asBool(const QByteArray &data, quint32 offset);
+    static bool asBool(const QByteArray &data, quint32 *offset);
+    static quint16 asUint16(const QByteArray &data, quint32 offset);
+    static quint16 asUint16(const QByteArray &data, quint32 *offset);
+    static quint64 asUint64(const QByteArray &data, quint32 offset);
+    static quint64 asUint64(const QByteArray &data, quint32 *offset);
+    static quint32 asUint32(const QByteArray &data, quint32 offset);
+    static quint32 asUint32(const QByteArray &data, quint32 *offset);
+    static QByteArray asString(const QByteArray &data, quint32 offset);
+    static QByteArray asString(const QByteArray &data, quint32 *offset);
+    static QString asUserString(const QByteArray &data, quint32 *offset);
+    static SshNameList asNameList(const QByteArray &data, quint32 *offset);
+    static Botan::BigInt asBigInt(const QByteArray &data, quint32 *offset);
+
+    static QString asUserString(const QByteArray &rawString);
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHPACKETPARSER_P_H

+ 118 - 0
src/tool/serverctrl/qssh/sshpseudoterminal.h

@@ -0,0 +1,118 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHPSEUDOTERMINAL_H
+#define SSHPSEUDOTERMINAL_H
+
+#include "ssh_global.h"
+
+#include <QByteArray>
+#include <QHash>
+
+namespace QSsh {
+
+class QSSH_EXPORT SshPseudoTerminal
+{
+  public:
+    explicit SshPseudoTerminal(const QByteArray &termType = "vt100",
+            int rowCount = 24, int columnCount = 80)
+        : termType(termType), rowCount(rowCount), columnCount(columnCount) {}
+
+    QByteArray termType;
+    int rowCount;
+    int columnCount;
+
+    enum Mode {
+        VINTR = 1,      // Interrupt character.
+        VQUIT = 2,      // The quit character (sends SIGQUIT signal on POSIX systems).
+        VERASE = 3,     // Erase the character to left of the cursor.
+        VKILL = 4,      // Kill the current input line.
+        VEOF = 5,       // End-of-file character (sends EOF from the terminal).
+        VEOL = 6,       // End-of-line character in addition to carriage return and/or linefeed.
+        VEOL2 = 7,      // Additional end-of-line character.
+        VSTART = 8,     // Continues paused output (normally control-Q).
+        VSTOP = 9,      // Pauses output (normally control-S).
+        VSUSP = 10,     // Suspends the current program.
+        VDSUSP = 11,    // Another suspend character.
+        VREPRINT = 12,  // Reprints the current input line.
+        VWERASE = 13,   // Erases a word left of cursor.
+        VLNEXT = 14,    // Enter the next character typed literally, even if it is a special character.
+        VFLUSH = 15,    // Character to flush output.
+        VSWTCH = 16,    // Switch to a different shell layer.
+        VSTATUS = 17,   // Prints system status line (load, command, pid, etc).
+        VDISCARD = 18,  // Toggles the flushing of terminal output.
+
+        IGNPAR = 30,    // The ignore parity flag.  The parameter SHOULD be 0 if this flag is FALSE, and 1 if it is TRUE.
+        PARMRK = 31,    // Mark parity and framing errors.
+        INPCK = 32,     // Enable checking of parity errors.
+        ISTRIP = 33,    // Strip 8th bit off characters.
+        INLCR = 34,     // Map NL into CR on input.
+        IGNCR = 35,     // Ignore CR on input.
+        ICRNL = 36,     // Map CR to NL on input.
+        IUCLC = 37,     // Translate uppercase characters to lowercase.
+        IXON = 38,      // Enable output flow control.
+        IXANY = 39,     // Any char will restart after stop.
+        IXOFF = 40,     // Enable input flow control.
+        IMAXBEL = 41,   // Ring bell on input queue full.
+        ISIG = 50,      // Enable signals INTR, QUIT, [D]SUSP.
+        ICANON = 51,    // Canonicalize input lines.
+        XCASE = 52,     // Enable input and output of uppercase characters by preceding their lowercase equivalents with "\".
+        ECHO = 53,      // Enable echoing.
+        ECHOE = 54,     // Visually erase chars.
+        ECHOK = 55,     // Kill character discards current line.
+        ECHONL = 56,    // Echo NL even if ECHO is off.
+        NOFLSH = 57,    // Don't flush after interrupt.
+        TOSTOP = 58,    // Stop background jobs from output.
+        IEXTEN = 59,    // Enable extensions.
+        ECHOCTL = 60,   // Echo control characters as ^(Char).
+        ECHOKE = 61,    // Visual erase for line kill.
+        PENDIN = 62,    // Retype pending input.
+        OPOST = 70,     // Enable output processing.
+        OLCUC = 71,     // Convert lowercase to uppercase.
+        ONLCR = 72,     // Map NL to CR-NL.
+        OCRNL = 73,     // Translate carriage return to newline (output).
+        ONOCR = 74,     // Translate newline to carriage return-newline (output).
+        ONLRET = 75,    // Newline performs a carriage return (output).
+        CS7 = 90,       // 7 bit mode.
+        CS8 = 91,       // 8 bit mode.
+        PARENB = 92,    // Parity enable.
+        PARODD = 93,    // Odd parity, else even.
+
+        TTY_OP_ISPEED = 128,  // Specifies the input baud rate in bits per second.
+        TTY_OP_OSPEED = 129   // Specifies the output baud rate in bits per second.
+    };
+
+    typedef QHash<Mode, quint32> ModeMap;
+    ModeMap modes;
+};
+
+} // namespace QSsh
+
+#endif // SSHPSEUDOTERMINAL_H

+ 401 - 0
src/tool/serverctrl/qssh/sshremoteprocess.cpp

@@ -0,0 +1,401 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshremoteprocess.h"
+#include "sshremoteprocess_p.h"
+#include "sshlogging_p.h"
+
+#include "ssh_global.h"
+#include "sshincomingpacket_p.h"
+#include "sshsendfacility_p.h"
+#include "sshx11displayinfo_p.h"
+
+#include <botan/exceptn.h>
+
+#include <QTimer>
+
+#include <cstring>
+#include <cstdlib>
+
+namespace QSsh {
+
+const struct {
+    SshRemoteProcess::Signal signalEnum;
+    const char * const signalString;
+} signalMap[] = {
+    {SshRemoteProcess::AbrtSignal, "ABRT"}, {SshRemoteProcess::AlrmSignal, "ALRM"},
+    {SshRemoteProcess::FpeSignal, "FPE"}, {SshRemoteProcess::HupSignal, "HUP"},
+    {SshRemoteProcess::IllSignal, "ILL"}, {SshRemoteProcess::IntSignal, "INT"},
+    {SshRemoteProcess::KillSignal, "KILL"}, {SshRemoteProcess::PipeSignal, "PIPE"},
+    {SshRemoteProcess::QuitSignal, "QUIT"}, {SshRemoteProcess::SegvSignal, "SEGV"},
+    {SshRemoteProcess::TermSignal, "TERM"}, {SshRemoteProcess::Usr1Signal, "USR1"},
+    {SshRemoteProcess::Usr2Signal, "USR2"}
+};
+
+SshRemoteProcess::SshRemoteProcess(const QByteArray &command, quint32 channelId,
+    Internal::SshSendFacility &sendFacility)
+    : d(new Internal::SshRemoteProcessPrivate(command, channelId, sendFacility, this))
+{
+    init();
+}
+
+SshRemoteProcess::SshRemoteProcess(quint32 channelId, Internal::SshSendFacility &sendFacility)
+    : d(new Internal::SshRemoteProcessPrivate(channelId, sendFacility, this))
+{
+    init();
+}
+
+SshRemoteProcess::~SshRemoteProcess()
+{
+    QSSH_ASSERT(d->channelState() != Internal::AbstractSshChannel::SessionEstablished);
+    SshRemoteProcess::close();
+    delete d;
+}
+
+bool SshRemoteProcess::atEnd() const
+{
+    return QIODevice::atEnd() && d->data().isEmpty();
+}
+
+qint64 SshRemoteProcess::bytesAvailable() const
+{
+    return QIODevice::bytesAvailable() + d->data().count();
+}
+
+bool SshRemoteProcess::canReadLine() const
+{
+    return QIODevice::canReadLine() || d->data().contains('\n');
+}
+
+QByteArray SshRemoteProcess::readAllStandardOutput()
+{
+    return readAllFromChannel(QProcess::StandardOutput);
+}
+
+QByteArray SshRemoteProcess::readAllStandardError()
+{
+    return readAllFromChannel(QProcess::StandardError);
+}
+
+QByteArray SshRemoteProcess::readAllFromChannel(QProcess::ProcessChannel channel)
+{
+    const QProcess::ProcessChannel currentReadChannel = readChannel();
+    setReadChannel(channel);
+    const QByteArray &data = readAll();
+    setReadChannel(currentReadChannel);
+    return data;
+}
+
+void SshRemoteProcess::close()
+{
+    d->closeChannel();
+    QIODevice::close();
+}
+
+qint64 SshRemoteProcess::readData(char *data, qint64 maxlen)
+{
+    const qint64 bytesRead = qMin(qint64(d->data().count()), maxlen);
+    memcpy(data, d->data().constData(), bytesRead);
+    d->data().remove(0, bytesRead);
+    return bytesRead;
+}
+
+qint64 SshRemoteProcess::writeData(const char *data, qint64 len)
+{
+    if (isRunning()) {
+        d->sendData(QByteArray(data, len));
+        return len;
+    }
+    return 0;
+}
+
+QProcess::ProcessChannel SshRemoteProcess::readChannel() const
+{
+    return d->m_readChannel;
+}
+
+void SshRemoteProcess::setReadChannel(QProcess::ProcessChannel channel)
+{
+    d->m_readChannel = channel;
+}
+
+void SshRemoteProcess::init()
+{
+    connect(d, &Internal::SshRemoteProcessPrivate::started,
+            this, &SshRemoteProcess::started, Qt::QueuedConnection);
+    connect(d, &Internal::SshRemoteProcessPrivate::readyReadStandardOutput,
+            this, &SshRemoteProcess::readyReadStandardOutput, Qt::QueuedConnection);
+    connect(d, &Internal::SshRemoteProcessPrivate::readyRead,
+            this, &SshRemoteProcess::readyRead, Qt::QueuedConnection);
+    connect(d, &Internal::SshRemoteProcessPrivate::readyReadStandardError,
+            this, &SshRemoteProcess::readyReadStandardError, Qt::QueuedConnection);
+    connect(d, &Internal::SshRemoteProcessPrivate::closed,
+            this, &SshRemoteProcess::closed, Qt::QueuedConnection);
+    connect(d, &Internal::SshRemoteProcessPrivate::eof,
+            this, &SshRemoteProcess::readChannelFinished, Qt::QueuedConnection);
+}
+
+void SshRemoteProcess::addToEnvironment(const QByteArray &var, const QByteArray &value)
+{
+    if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive)
+        d->m_env << qMakePair(var, value); // Cached locally and sent on start()
+}
+
+void SshRemoteProcess::clearEnvironment()
+{
+    d->m_env.clear();
+}
+
+void SshRemoteProcess::requestTerminal(const SshPseudoTerminal &terminal)
+{
+    QSSH_ASSERT_AND_RETURN(d->channelState() == Internal::SshRemoteProcessPrivate::Inactive);
+    d->m_useTerminal = true;
+    d->m_terminal = terminal;
+}
+
+void SshRemoteProcess::requestX11Forwarding(const QString &displayName)
+{
+    QSSH_ASSERT_AND_RETURN(d->channelState() == Internal::SshRemoteProcessPrivate::Inactive);
+    d->m_x11DisplayName = displayName;
+}
+
+void SshRemoteProcess::start()
+{
+    if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive) {
+        qCDebug(Internal::sshLog, "process start requested, channel id = %u", d->localChannelId());
+        QIODevice::open(QIODevice::ReadWrite);
+        d->requestSessionStart();
+    }
+}
+
+void SshRemoteProcess::sendSignal(Signal signal)
+{
+    try {
+        if (isRunning()) {
+            const char *signalString = nullptr;
+            for (size_t i = 0; i < sizeof signalMap/sizeof *signalMap && !signalString; ++i) {
+                if (signalMap[i].signalEnum == signal)
+                    signalString = signalMap[i].signalString;
+            }
+            QSSH_ASSERT_AND_RETURN(signalString);
+            d->m_sendFacility.sendChannelSignalPacket(d->remoteChannel(), signalString);
+        }
+    }  catch (const std::exception &e) {
+        setErrorString(QString::fromLocal8Bit(e.what()));
+        d->closeChannel();
+    }
+}
+
+bool SshRemoteProcess::isRunning() const
+{
+    return d->m_procState == Internal::SshRemoteProcessPrivate::Running;
+}
+
+int SshRemoteProcess::exitCode() const { return d->m_exitCode; }
+
+SshRemoteProcess::Signal SshRemoteProcess::exitSignal() const
+{
+    return static_cast<SshRemoteProcess::Signal>(d->m_signal);
+}
+
+namespace Internal {
+
+void SshRemoteProcessPrivate::failToStart(const QString &reason)
+{
+    if (m_procState != NotYetStarted)
+        return;
+    m_proc->setErrorString(reason);
+    setProcState(StartFailed);
+}
+
+SshRemoteProcessPrivate::SshRemoteProcessPrivate(const QByteArray &command,
+        quint32 channelId, SshSendFacility &sendFacility, SshRemoteProcess *proc)
+    : AbstractSshChannel(channelId, sendFacility),
+      m_command(command),
+      m_isShell(false),
+      m_useTerminal(false),
+      m_proc(proc)
+{
+    init();
+}
+
+SshRemoteProcessPrivate::SshRemoteProcessPrivate(quint32 channelId, SshSendFacility &sendFacility,
+            SshRemoteProcess *proc)
+    : AbstractSshChannel(channelId, sendFacility),
+      m_isShell(true),
+      m_useTerminal(true),
+      m_proc(proc)
+{
+    init();
+}
+
+void SshRemoteProcessPrivate::init()
+{
+    m_procState = NotYetStarted;
+    m_wasRunning = false;
+    m_exitCode = 0;
+    m_readChannel = QProcess::StandardOutput;
+    m_signal = SshRemoteProcess::NoSignal;
+}
+
+void SshRemoteProcessPrivate::setProcState(ProcessState newState)
+{
+    qCDebug(sshLog, "channel: old state = %d,new state = %d", m_procState, newState);
+    m_procState = newState;
+    if (newState == StartFailed) {
+        emit closed(SshRemoteProcess::FailedToStart);
+    } else if (newState == Running) {
+        m_wasRunning = true;
+        emit started();
+    }
+}
+
+QByteArray &SshRemoteProcessPrivate::data()
+{
+    return m_readChannel == QProcess::StandardOutput ? m_stdout : m_stderr;
+}
+
+void SshRemoteProcessPrivate::closeHook()
+{
+    if (m_wasRunning) {
+        if (m_signal != SshRemoteProcess::NoSignal)
+            emit closed(SshRemoteProcess::CrashExit);
+        else
+            emit closed(SshRemoteProcess::NormalExit);
+    }
+}
+
+void SshRemoteProcessPrivate::handleOpenSuccessInternal()
+{
+    if (m_x11DisplayName.isEmpty())
+        startProcess(X11DisplayInfo());
+    else
+        emit x11ForwardingRequested(m_x11DisplayName);
+}
+
+void SshRemoteProcessPrivate::startProcess(const X11DisplayInfo &displayInfo)
+{
+    if (m_procState != NotYetStarted)
+        return;
+
+    for (const EnvVar &envVar : m_env) {
+        m_sendFacility.sendEnvPacket(remoteChannel(), envVar.first,
+            envVar.second);
+    }
+
+    if (!m_x11DisplayName.isEmpty()) {
+        m_sendFacility.sendX11ForwardingPacket(remoteChannel(), displayInfo.protocol,
+                                               displayInfo.randomCookie.toHex(), 0);
+    }
+
+    if (m_useTerminal)
+        m_sendFacility.sendPtyRequestPacket(remoteChannel(), m_terminal);
+
+    if (m_isShell)
+        m_sendFacility.sendShellPacket(remoteChannel());
+    else
+        m_sendFacility.sendExecPacket(remoteChannel(), m_command);
+    setProcState(ExecRequested);
+    m_timeoutTimer.start(ReplyTimeout);
+}
+
+void SshRemoteProcessPrivate::handleOpenFailureInternal(const QString &reason)
+{
+    failToStart(reason);
+}
+
+void SshRemoteProcessPrivate::handleChannelSuccess()
+{
+    if (m_procState != ExecRequested)  {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_MSG_CHANNEL_SUCCESS message.");
+    }
+    m_timeoutTimer.stop();
+    setProcState(Running);
+}
+
+void SshRemoteProcessPrivate::handleChannelFailure()
+{
+    if (m_procState != ExecRequested)  {
+        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_MSG_CHANNEL_FAILURE message.");
+    }
+    m_timeoutTimer.stop();
+    setProcState(StartFailed);
+    closeChannel();
+}
+
+void SshRemoteProcessPrivate::handleChannelDataInternal(const QByteArray &data)
+{
+    m_stdout += data;
+    emit readyReadStandardOutput();
+    if (m_readChannel == QProcess::StandardOutput)
+        emit readyRead();
+}
+
+void SshRemoteProcessPrivate::handleChannelExtendedDataInternal(quint32 type,
+    const QByteArray &data)
+{
+    if (type != SSH_EXTENDED_DATA_STDERR) {
+        qCWarning(sshLog, "Unknown extended data type %u", type);
+    } else {
+        m_stderr += data;
+        emit readyReadStandardError();
+        if (m_readChannel == QProcess::StandardError)
+            emit readyRead();
+    }
+}
+
+void SshRemoteProcessPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
+{
+    qCDebug(sshLog, "Process exiting with exit code %d", exitStatus.exitStatus);
+    m_exitCode = exitStatus.exitStatus;
+    m_procState = Exited;
+}
+
+void SshRemoteProcessPrivate::handleExitSignal(const SshChannelExitSignal &signal)
+{
+    qCDebug(sshLog, "Exit due to signal %s", signal.signal.data());
+
+    for (size_t i = 0; i < sizeof signalMap/sizeof *signalMap; ++i) {
+        if (signalMap[i].signalString == signal.signal) {
+            m_signal = signalMap[i].signalEnum;
+            m_procState = Exited;
+            m_proc->setErrorString(tr("Process killed by signal"));
+            return;
+        }
+    }
+
+    throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, "Invalid signal",
+        tr("Server sent invalid signal \"%1\"").arg(QString::fromUtf8(signal.signal)));
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 143 - 0
src/tool/serverctrl/qssh/sshremoteprocess.h

@@ -0,0 +1,143 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHREMOTECOMMAND_H
+#define SSHREMOTECOMMAND_H
+
+#include "ssh_global.h"
+
+#include <QProcess>
+#include <QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QByteArray;
+QT_END_NAMESPACE
+
+namespace QSsh {
+class SshPseudoTerminal;
+namespace Internal {
+class SshChannelManager;
+class SshRemoteProcessPrivate;
+class SshSendFacility;
+} // namespace Internal
+
+/*!
+    \class QSsh::SshRemoteProcess
+
+    \brief This class implements an SSH channel for running a remote process.
+
+    Objects are created via SshConnection::createRemoteProcess.
+    The process is started via the start() member function.
+    If the process needs a pseudo terminal, you can request one
+    via requestTerminal() before calling start().
+    Note that this class does not support QIODevice's waitFor*() functions, i.e. it has
+    no synchronous mode.
+ */
+
+// TODO: ProcessChannel
+class QSSH_EXPORT SshRemoteProcess : public QIODevice
+{
+    Q_OBJECT
+
+    friend class Internal::SshChannelManager;
+    friend class Internal::SshRemoteProcessPrivate;
+
+public:
+    typedef QSharedPointer<SshRemoteProcess> Ptr;
+    enum ExitStatus { FailedToStart, CrashExit, NormalExit };
+    enum Signal {
+        AbrtSignal, AlrmSignal, FpeSignal, HupSignal, IllSignal, IntSignal, KillSignal, PipeSignal,
+        QuitSignal, SegvSignal, TermSignal, Usr1Signal, Usr2Signal, NoSignal
+    };
+
+    ~SshRemoteProcess();
+
+    // QIODevice stuff
+    bool atEnd() const;
+    qint64 bytesAvailable() const;
+    bool canReadLine() const;
+    void close();
+    bool isSequential() const { return true; }
+
+    QProcess::ProcessChannel readChannel() const;
+    void setReadChannel(QProcess::ProcessChannel channel);
+
+    /*
+     * Note that this is of limited value in practice, because servers are
+     * usually configured to ignore such requests for security reasons.
+     */
+    void addToEnvironment(const QByteArray &var, const QByteArray &value);
+    void clearEnvironment();
+
+    void requestTerminal(const SshPseudoTerminal &terminal);
+    void requestX11Forwarding(const QString &displayName);
+    void start();
+
+    bool isRunning() const;
+    int exitCode() const;
+    Signal exitSignal() const;
+
+    QByteArray readAllStandardOutput();
+    QByteArray readAllStandardError();
+
+    // Note: This is ignored by the OpenSSH server.
+    void sendSignal(Signal signal);
+    void kill() { sendSignal(KillSignal); }
+
+signals:
+    void started();
+
+    void readyReadStandardOutput();
+    void readyReadStandardError();
+
+    /*
+     * Parameter is of type ExitStatus, but we use int because of
+     * signal/slot awkwardness (full namespace required).
+     */
+    void closed(int exitStatus);
+
+private:
+    SshRemoteProcess(const QByteArray &command, quint32 channelId,
+        Internal::SshSendFacility &sendFacility);
+    SshRemoteProcess(quint32 channelId, Internal::SshSendFacility &sendFacility);
+
+    // QIODevice stuff
+    qint64 readData(char *data, qint64 maxlen);
+    qint64 writeData(const char *data, qint64 len);
+
+    void init();
+    QByteArray readAllFromChannel(QProcess::ProcessChannel channel);
+
+    Internal::SshRemoteProcessPrivate *d;
+};
+
+} // namespace QSsh
+
+#endif // SSHREMOTECOMMAND_H

+ 118 - 0
src/tool/serverctrl/qssh/sshremoteprocess_p.h

@@ -0,0 +1,118 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHREMOTEPROCESS_P_H
+#define SSHREMOTEPROCESS_P_H
+
+#include "sshpseudoterminal.h"
+
+#include "sshchannel_p.h"
+
+#include <QList>
+#include <QPair>
+#include <QProcess>
+
+namespace QSsh {
+class SshRemoteProcess;
+
+namespace Internal {
+class SshSendFacility;
+class X11DisplayInfo;
+
+class SshRemoteProcessPrivate : public AbstractSshChannel
+{
+    Q_OBJECT
+    friend class QSsh::SshRemoteProcess;
+public:
+    enum ProcessState {
+        NotYetStarted, ExecRequested, StartFailed, Running, Exited
+    };
+
+    void failToStart(const QString &reason);
+    void startProcess(const X11DisplayInfo &displayInfo);
+
+signals:
+    void started();
+    void readyRead();
+    void readyReadStandardOutput();
+    void readyReadStandardError();
+    void closed(int exitStatus);
+    void x11ForwardingRequested(const QString &display);
+
+private:
+    SshRemoteProcessPrivate(const QByteArray &command, quint32 channelId,
+        SshSendFacility &sendFacility, SshRemoteProcess *proc);
+    SshRemoteProcessPrivate(quint32 channelId, SshSendFacility &sendFacility,
+        SshRemoteProcess *proc);
+
+    virtual void handleChannelSuccess();
+    virtual void handleChannelFailure();
+
+    virtual void handleOpenSuccessInternal();
+    virtual void handleOpenFailureInternal(const QString &reason);
+    virtual void handleChannelDataInternal(const QByteArray &data);
+    virtual void handleChannelExtendedDataInternal(quint32 type,
+        const QByteArray &data);
+    virtual void handleExitStatus(const SshChannelExitStatus &exitStatus);
+    virtual void handleExitSignal(const SshChannelExitSignal &signal);
+
+    virtual void closeHook();
+
+    void init();
+    void setProcState(ProcessState newState);
+    QByteArray &data();
+
+    QProcess::ProcessChannel m_readChannel;
+
+    ProcessState m_procState;
+    bool m_wasRunning;
+    int m_signal;
+    int m_exitCode;
+
+    const QByteArray m_command;
+    const bool m_isShell;
+
+    typedef QPair<QByteArray, QByteArray> EnvVar;
+    QList<EnvVar> m_env;
+    bool m_useTerminal;
+    SshPseudoTerminal m_terminal;
+
+    QString m_x11DisplayName;
+
+    QByteArray m_stdout;
+    QByteArray m_stderr;
+
+    SshRemoteProcess *m_proc;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHREMOTEPROCESS_P_H

+ 283 - 0
src/tool/serverctrl/qssh/sshremoteprocessrunner.cpp

@@ -0,0 +1,283 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshremoteprocessrunner.h"
+
+#include "sshconnectionmanager.h"
+#include "sshpseudoterminal.h"
+
+namespace QSsh {
+namespace Internal {
+namespace {
+enum State { Inactive, Connecting, Connected, ProcessRunning };
+} // anonymous namespace
+
+class SshRemoteProcessRunnerPrivate
+{
+public:
+    SshRemoteProcessRunnerPrivate() : m_state(Inactive) {}
+
+    SshRemoteProcess::Ptr m_process;
+    SshConnection *m_connection;
+    bool m_runInTerminal;
+    SshPseudoTerminal m_terminal;
+    QByteArray m_command;
+    QSsh::SshError m_lastConnectionError;
+    QString m_lastConnectionErrorString;
+    SshRemoteProcess::ExitStatus m_exitStatus;
+    SshRemoteProcess::Signal m_exitSignal;
+    QByteArray m_stdout;
+    QByteArray m_stderr;
+    int m_exitCode;
+    QString m_processErrorString;
+    State m_state;
+};
+
+} // namespace Internal
+
+using namespace Internal;
+
+SshRemoteProcessRunner::SshRemoteProcessRunner(QObject *parent)
+    : QObject(parent), d(new SshRemoteProcessRunnerPrivate)
+{
+}
+
+SshRemoteProcessRunner::~SshRemoteProcessRunner()
+{
+    disconnect();
+    setState(Inactive);
+    delete d;
+}
+
+void SshRemoteProcessRunner::run(const QByteArray &command,
+    const SshConnectionParameters &sshParams)
+{
+    QSSH_ASSERT_AND_RETURN(d->m_state == Inactive);
+
+    d->m_runInTerminal = false;
+    runInternal(command, sshParams);
+}
+
+void SshRemoteProcessRunner::runInTerminal(const QByteArray &command,
+    const SshPseudoTerminal &terminal, const SshConnectionParameters &sshParams)
+{
+    d->m_terminal = terminal;
+    d->m_runInTerminal = true;
+    runInternal(command, sshParams);
+}
+
+void SshRemoteProcessRunner::runInternal(const QByteArray &command,
+    const SshConnectionParameters &sshParams)
+{
+    setState(Connecting);
+
+    d->m_lastConnectionError = SshNoError;
+    d->m_lastConnectionErrorString.clear();
+    d->m_processErrorString.clear();
+    d->m_exitSignal = SshRemoteProcess::NoSignal;
+    d->m_exitCode = -1;
+    d->m_command = command;
+    d->m_connection = QSsh::acquireConnection(sshParams);
+    connect(d->m_connection, &SshConnection::error,
+            this, &SshRemoteProcessRunner::handleConnectionError);
+    connect(d->m_connection, &SshConnection::disconnected,
+            this, &SshRemoteProcessRunner::handleDisconnected);
+    if (d->m_connection->state() == SshConnection::Connected) {
+        handleConnected();
+    } else {
+        connect(d->m_connection, &SshConnection::connected, this, &SshRemoteProcessRunner::handleConnected);
+        if (d->m_connection->state() == SshConnection::Unconnected)
+            d->m_connection->connectToHost();
+    }
+}
+
+void SshRemoteProcessRunner::handleConnected()
+{
+    QSSH_ASSERT_AND_RETURN(d->m_state == Connecting);
+    setState(Connected);
+
+    d->m_process = d->m_connection->createRemoteProcess(d->m_command);
+    connect(d->m_process.data(), &SshRemoteProcess::started,
+            this, &SshRemoteProcessRunner::handleProcessStarted);
+    connect(d->m_process.data(), &SshRemoteProcess::closed,
+            this, &SshRemoteProcessRunner::handleProcessFinished);
+    connect(d->m_process.data(), &SshRemoteProcess::readyReadStandardOutput,
+            this, &SshRemoteProcessRunner::handleStdout);
+    connect(d->m_process.data(), &SshRemoteProcess::readyReadStandardError,
+            this, &SshRemoteProcessRunner::handleStderr);
+    if (d->m_runInTerminal)
+        d->m_process->requestTerminal(d->m_terminal);
+    d->m_process->start();
+}
+
+void SshRemoteProcessRunner::handleConnectionError(QSsh::SshError error)
+{
+    d->m_lastConnectionError = error;
+    d->m_lastConnectionErrorString = d->m_connection->errorString();
+    handleDisconnected();
+    emit connectionError();
+}
+
+void SshRemoteProcessRunner::handleDisconnected()
+{
+    QSSH_ASSERT_AND_RETURN(d->m_state == Connecting || d->m_state == Connected
+        || d->m_state == ProcessRunning);
+    setState(Inactive);
+}
+
+void SshRemoteProcessRunner::handleProcessStarted()
+{
+    QSSH_ASSERT_AND_RETURN(d->m_state == Connected);
+
+    setState(ProcessRunning);
+    emit processStarted();
+}
+
+void SshRemoteProcessRunner::handleProcessFinished(int exitStatus)
+{
+    d->m_exitStatus = static_cast<SshRemoteProcess::ExitStatus>(exitStatus);
+    switch (d->m_exitStatus) {
+    case SshRemoteProcess::FailedToStart:
+        QSSH_ASSERT_AND_RETURN(d->m_state == Connected);
+        break;
+    case SshRemoteProcess::CrashExit:
+        QSSH_ASSERT_AND_RETURN(d->m_state == ProcessRunning);
+        d->m_exitSignal = d->m_process->exitSignal();
+        break;
+    case SshRemoteProcess::NormalExit:
+        QSSH_ASSERT_AND_RETURN(d->m_state == ProcessRunning);
+        d->m_exitCode = d->m_process->exitCode();
+        break;
+    default:
+        Q_ASSERT_X(false, Q_FUNC_INFO, "Impossible exit status.");
+    }
+    d->m_processErrorString = d->m_process->errorString();
+    setState(Inactive);
+    emit processClosed(exitStatus);
+}
+
+void SshRemoteProcessRunner::handleStdout()
+{
+    d->m_stdout += d->m_process->readAllStandardOutput();
+    emit readyReadStandardOutput();
+}
+
+void SshRemoteProcessRunner::handleStderr()
+{
+    d->m_stderr += d->m_process->readAllStandardError();
+    emit readyReadStandardError();
+}
+
+void SshRemoteProcessRunner::setState(int newState)
+{
+    if (d->m_state == newState)
+        return;
+
+    d->m_state = static_cast<State>(newState);
+    if (d->m_state == Inactive) {
+        if (d->m_process) {
+            disconnect(d->m_process.data(), nullptr, this, nullptr);
+            d->m_process->close();
+            d->m_process.clear();
+        }
+        if (d->m_connection) {
+            disconnect(d->m_connection, nullptr, this, nullptr);
+            QSsh::releaseConnection(d->m_connection);
+            d->m_connection = nullptr;
+        }
+    }
+}
+
+QByteArray SshRemoteProcessRunner::command() const { return d->m_command; }
+SshError SshRemoteProcessRunner::lastConnectionError() const { return d->m_lastConnectionError; }
+QString SshRemoteProcessRunner::lastConnectionErrorString() const {
+    return d->m_lastConnectionErrorString;
+}
+
+bool SshRemoteProcessRunner::isProcessRunning() const
+{
+    return d->m_process && d->m_process->isRunning();
+}
+
+SshRemoteProcess::ExitStatus SshRemoteProcessRunner::processExitStatus() const
+{
+    QSSH_ASSERT(!isProcessRunning());
+    return d->m_exitStatus;
+}
+
+SshRemoteProcess::Signal SshRemoteProcessRunner::processExitSignal() const
+{
+    QSSH_ASSERT(processExitStatus() == SshRemoteProcess::CrashExit);
+    return d->m_exitSignal;
+}
+
+int SshRemoteProcessRunner::processExitCode() const
+{
+    QSSH_ASSERT(processExitStatus() == SshRemoteProcess::NormalExit);
+    return d->m_exitCode;
+}
+
+QString SshRemoteProcessRunner::processErrorString() const
+{
+    return d->m_processErrorString;
+}
+
+QByteArray SshRemoteProcessRunner::readAllStandardOutput()
+{
+    const QByteArray data = d->m_stdout;
+    d->m_stdout.clear();
+    return data;
+}
+
+QByteArray SshRemoteProcessRunner::readAllStandardError()
+{
+    const QByteArray data = d->m_stderr;
+    d->m_stderr.clear();
+    return data;
+}
+
+void SshRemoteProcessRunner::writeDataToProcess(const QByteArray &data)
+{
+    QSSH_ASSERT(isProcessRunning());
+    d->m_process->write(data);
+}
+
+void SshRemoteProcessRunner::sendSignalToProcess(SshRemoteProcess::Signal signal)
+{
+    QSSH_ASSERT(isProcessRunning());
+    d->m_process->sendSignal(signal);
+}
+
+void SshRemoteProcessRunner::cancel()
+{
+    setState(Inactive);
+}
+
+} // namespace QSsh

+ 98 - 0
src/tool/serverctrl/qssh/sshremoteprocessrunner.h

@@ -0,0 +1,98 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHREMOTEPROCESSRUNNER_H
+#define SSHREMOTEPROCESSRUNNER_H
+
+#include "sshconnection.h"
+#include "sshremoteprocess.h"
+
+namespace QSsh {
+namespace Internal {
+class SshRemoteProcessRunnerPrivate;
+} // namespace Internal
+
+/*!
+    \class QSsh::SshRemoteProcessRunner
+
+    \brief Convenience class for running a remote process over an SSH connection.
+*/
+
+class QSSH_EXPORT SshRemoteProcessRunner : public QObject
+{
+    Q_OBJECT
+
+public:
+    SshRemoteProcessRunner(QObject *parent = nullptr);
+    ~SshRemoteProcessRunner();
+
+    void run(const QByteArray &command, const SshConnectionParameters &sshParams);
+    void runInTerminal(const QByteArray &command, const SshPseudoTerminal &terminal,
+        const SshConnectionParameters &sshParams);
+    QByteArray command() const;
+
+    QSsh::SshError lastConnectionError() const;
+    QString lastConnectionErrorString() const;
+
+    bool isProcessRunning() const;
+    void writeDataToProcess(const QByteArray &data);
+    void sendSignalToProcess(SshRemoteProcess::Signal signal); // No effect with OpenSSH server.
+    void cancel(); // Does not stop remote process, just frees SSH-related process resources.
+    SshRemoteProcess::ExitStatus processExitStatus() const;
+    SshRemoteProcess::Signal processExitSignal() const;
+    int processExitCode() const;
+    QString processErrorString() const;
+    QByteArray readAllStandardOutput();
+    QByteArray readAllStandardError();
+
+signals:
+    void connectionError();
+    void processStarted();
+    void readyReadStandardOutput();
+    void readyReadStandardError();
+    void processClosed(int exitStatus); // values are of type SshRemoteProcess::ExitStatus
+
+private:
+    void handleConnected();
+    void handleConnectionError(QSsh::SshError error);
+    void handleDisconnected();
+    void handleProcessStarted();
+    void handleProcessFinished(int exitStatus);
+    void handleStdout();
+    void handleStderr();
+    void runInternal(const QByteArray &command, const QSsh::SshConnectionParameters &sshParams);
+    void setState(int newState);
+
+    Internal::SshRemoteProcessRunnerPrivate * const d;
+};
+
+} // namespace QSsh
+
+#endif // SSHREMOTEPROCESSRUNNER_H

+ 288 - 0
src/tool/serverctrl/qssh/sshsendfacility.cpp

@@ -0,0 +1,288 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#include "sshsendfacility_p.h"
+
+#include "sshkeyexchange_p.h"
+#include "sshlogging_p.h"
+#include "sshoutgoingpacket_p.h"
+
+#include <QTcpSocket>
+
+namespace QSsh {
+namespace Internal {
+
+SshSendFacility::SshSendFacility(QTcpSocket *socket)
+    : m_clientSeqNr(0), m_socket(socket),
+      m_outgoingPacket(m_encrypter, m_clientSeqNr)
+{
+}
+
+void SshSendFacility::sendPacket()
+{
+    qCDebug(sshLog, "Sending packet, client seq nr is %u", m_clientSeqNr);
+    if (m_socket->isValid()
+        && m_socket->state() == QAbstractSocket::ConnectedState) {
+        m_socket->write(m_outgoingPacket.rawData());
+        ++m_clientSeqNr;
+    }
+}
+
+void SshSendFacility::reset()
+{
+    m_clientSeqNr = 0;
+    m_encrypter.clearKeys();
+}
+
+void SshSendFacility::recreateKeys(const SshKeyExchange &keyExchange)
+{
+    m_encrypter.recreateKeys(keyExchange);
+}
+
+void SshSendFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
+{
+    m_encrypter.createAuthenticationKey(privKeyFileContents);
+}
+
+QByteArray SshSendFacility::sendKeyExchangeInitPacket()
+{
+    const QByteArray &payLoad = m_outgoingPacket.generateKeyExchangeInitPacket();
+    sendPacket();
+    return payLoad;
+}
+
+void SshSendFacility::sendKeyDhInitPacket(const Botan::BigInt &e)
+{
+    m_outgoingPacket.generateKeyDhInitPacket(e);
+    sendPacket();
+}
+
+void SshSendFacility::sendKeyEcdhInitPacket(const QByteArray &clientQ)
+{
+    m_outgoingPacket.generateKeyEcdhInitPacket(clientQ);
+    sendPacket();
+}
+
+void SshSendFacility::sendNewKeysPacket()
+{
+    m_outgoingPacket.generateNewKeysPacket();
+    sendPacket();
+}
+
+void SshSendFacility::sendDisconnectPacket(SshErrorCode reason,
+    const QByteArray &reasonString)
+{
+    m_outgoingPacket.generateDisconnectPacket(reason, reasonString);
+    sendPacket();
+    }
+
+void SshSendFacility::sendMsgUnimplementedPacket(quint32 serverSeqNr)
+{
+    m_outgoingPacket.generateMsgUnimplementedPacket(serverSeqNr);
+    sendPacket();
+}
+
+void SshSendFacility::sendUserAuthServiceRequestPacket()
+{
+    m_outgoingPacket.generateUserAuthServiceRequestPacket();
+    sendPacket();
+}
+
+void SshSendFacility::sendUserAuthByPasswordRequestPacket(const QByteArray &user,
+    const QByteArray &service, const QByteArray &pwd)
+{
+    m_outgoingPacket.generateUserAuthByPasswordRequestPacket(user, service, pwd);
+    sendPacket();
+    }
+
+void SshSendFacility::sendUserAuthByPublicKeyRequestPacket(const QByteArray &user,
+    const QByteArray &service, const QByteArray &key, const QByteArray &signature)
+{
+    m_outgoingPacket.generateUserAuthByPublicKeyRequestPacket(user, service, key, signature);
+    sendPacket();
+}
+
+void SshSendFacility::sendQueryPublicKeyPacket(const QByteArray &user, const QByteArray &service,
+                                               const QByteArray &publicKey)
+{
+    m_outgoingPacket.generateQueryPublicKeyPacket(user, service, publicKey);
+    sendPacket();
+}
+
+void SshSendFacility::sendUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
+                                                                     const QByteArray &service)
+{
+    m_outgoingPacket.generateUserAuthByKeyboardInteractiveRequestPacket(user, service);
+    sendPacket();
+}
+
+void SshSendFacility::sendUserAuthInfoResponsePacket(const QStringList &responses)
+{
+    m_outgoingPacket.generateUserAuthInfoResponsePacket(responses);
+    sendPacket();
+}
+
+void SshSendFacility::sendRequestFailurePacket()
+{
+    m_outgoingPacket.generateRequestFailurePacket();
+    sendPacket();
+}
+
+void SshSendFacility::sendIgnorePacket()
+{
+    m_outgoingPacket.generateIgnorePacket();
+    sendPacket();
+}
+
+void SshSendFacility::sendInvalidPacket()
+{
+    m_outgoingPacket.generateInvalidMessagePacket();
+    sendPacket();
+}
+
+void SshSendFacility::sendSessionPacket(quint32 channelId, quint32 windowSize,
+    quint32 maxPacketSize)
+{
+    m_outgoingPacket.generateSessionPacket(channelId, windowSize,
+        maxPacketSize);
+    sendPacket();
+}
+
+void SshSendFacility::sendDirectTcpIpPacket(quint32 channelId, quint32 windowSize,
+    quint32 maxPacketSize, const QByteArray &remoteHost, quint32 remotePort,
+    const QByteArray &localIpAddress, quint32 localPort)
+{
+    m_outgoingPacket.generateDirectTcpIpPacket(channelId, windowSize, maxPacketSize, remoteHost,
+            remotePort, localIpAddress, localPort);
+    sendPacket();
+}
+
+void SshSendFacility::sendTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort)
+{
+    m_outgoingPacket.generateTcpIpForwardPacket(bindAddress, bindPort);
+    sendPacket();
+}
+
+void SshSendFacility::sendCancelTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort)
+{
+    m_outgoingPacket.generateCancelTcpIpForwardPacket(bindAddress, bindPort);
+    sendPacket();
+}
+
+void SshSendFacility::sendPtyRequestPacket(quint32 remoteChannel,
+    const SshPseudoTerminal &terminal)
+{
+    m_outgoingPacket.generatePtyRequestPacket(remoteChannel, terminal);
+    sendPacket();
+}
+
+void SshSendFacility::sendEnvPacket(quint32 remoteChannel,
+    const QByteArray &var, const QByteArray &value)
+{
+    m_outgoingPacket.generateEnvPacket(remoteChannel, var, value);
+    sendPacket();
+}
+
+void SshSendFacility::sendX11ForwardingPacket(quint32 remoteChannel, const QByteArray &protocol,
+                                              const QByteArray &cookie, quint32 screenNumber)
+{
+    m_outgoingPacket.generateX11ForwardingPacket(remoteChannel, protocol, cookie, screenNumber);
+    sendPacket();
+}
+
+void SshSendFacility::sendExecPacket(quint32 remoteChannel,
+    const QByteArray &command)
+{
+    m_outgoingPacket.generateExecPacket(remoteChannel, command);
+    sendPacket();
+}
+
+void SshSendFacility::sendShellPacket(quint32 remoteChannel)
+{
+    m_outgoingPacket.generateShellPacket(remoteChannel);
+    sendPacket();
+}
+
+void SshSendFacility::sendSftpPacket(quint32 remoteChannel)
+{
+    m_outgoingPacket.generateSftpPacket(remoteChannel);
+    sendPacket();
+}
+
+void SshSendFacility::sendWindowAdjustPacket(quint32 remoteChannel,
+    quint32 bytesToAdd)
+{
+    m_outgoingPacket.generateWindowAdjustPacket(remoteChannel, bytesToAdd);
+    sendPacket();
+}
+
+void SshSendFacility::sendChannelDataPacket(quint32 remoteChannel,
+    const QByteArray &data)
+{
+    m_outgoingPacket.generateChannelDataPacket(remoteChannel, data);
+    sendPacket();
+}
+
+void SshSendFacility::sendChannelSignalPacket(quint32 remoteChannel,
+    const QByteArray &signalName)
+{
+    m_outgoingPacket.generateChannelSignalPacket(remoteChannel, signalName);
+    sendPacket();
+}
+
+void SshSendFacility::sendChannelEofPacket(quint32 remoteChannel)
+{
+    m_outgoingPacket.generateChannelEofPacket(remoteChannel);
+    sendPacket();
+}
+
+void SshSendFacility::sendChannelClosePacket(quint32 remoteChannel)
+{
+    m_outgoingPacket.generateChannelClosePacket(remoteChannel);
+    sendPacket();
+}
+
+void SshSendFacility::sendChannelOpenConfirmationPacket(quint32 remoteChannel, quint32 localChannel,
+    quint32 localWindowSize, quint32 maxPacketSize)
+{
+    m_outgoingPacket.generateChannelOpenConfirmationPacket(remoteChannel, localChannel,
+                                                           localWindowSize, maxPacketSize);
+    sendPacket();
+}
+
+void SshSendFacility::sendChannelOpenFailurePacket(quint32 remoteChannel, quint32 reason,
+    const QByteArray &reasonString)
+{
+    m_outgoingPacket.generateChannelOpenFailurePacket(remoteChannel, reason, reasonString);
+    sendPacket();
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 122 - 0
src/tool/serverctrl/qssh/sshsendfacility_p.h

@@ -0,0 +1,122 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+#ifndef SSHCONNECTIONOUTSTATE_P_H
+#define SSHCONNECTIONOUTSTATE_P_H
+
+#include "sshcryptofacility_p.h"
+#include "sshoutgoingpacket_p.h"
+
+#include <QStringList>
+
+QT_BEGIN_NAMESPACE
+class QTcpSocket;
+QT_END_NAMESPACE
+
+
+namespace QSsh {
+class SshPseudoTerminal;
+
+namespace Internal {
+class SshKeyExchange;
+
+class SshSendFacility
+{
+public:
+    SshSendFacility(QTcpSocket *socket);
+    void reset();
+    void recreateKeys(const SshKeyExchange &keyExchange);
+    void createAuthenticationKey(const QByteArray &privKeyFileContents);
+
+    QByteArray sessionId() const { return m_encrypter.sessionId(); }
+
+    QByteArray sendKeyExchangeInitPacket();
+    void sendKeyDhInitPacket(const Botan::BigInt &e);
+    void sendKeyEcdhInitPacket(const QByteArray &clientQ);
+    void sendNewKeysPacket();
+    void sendDisconnectPacket(SshErrorCode reason,
+        const QByteArray &reasonString);
+    void sendMsgUnimplementedPacket(quint32 serverSeqNr);
+    void sendUserAuthServiceRequestPacket();
+    void sendUserAuthByPasswordRequestPacket(const QByteArray &user,
+        const QByteArray &service, const QByteArray &pwd);
+    void sendUserAuthByPublicKeyRequestPacket(const QByteArray &user,
+        const QByteArray &service, const QByteArray &key, const QByteArray &signature);
+    void sendQueryPublicKeyPacket(const QByteArray &user, const QByteArray &service,
+                                  const QByteArray &publicKey);
+    void sendUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
+        const QByteArray &service);
+    void sendUserAuthInfoResponsePacket(const QStringList &responses);
+    void sendRequestFailurePacket();
+    void sendIgnorePacket();
+    void sendInvalidPacket();
+    void sendSessionPacket(quint32 channelId, quint32 windowSize,
+        quint32 maxPacketSize);
+    void sendDirectTcpIpPacket(quint32 channelId, quint32 windowSize, quint32 maxPacketSize,
+        const QByteArray &remoteHost, quint32 remotePort, const QByteArray &localIpAddress,
+        quint32 localPort);
+    void sendTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort);
+    void sendCancelTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort);
+    void sendPtyRequestPacket(quint32 remoteChannel,
+        const SshPseudoTerminal &terminal);
+    void sendEnvPacket(quint32 remoteChannel, const QByteArray &var,
+        const QByteArray &value);
+    void sendX11ForwardingPacket(quint32 remoteChannel, const QByteArray &protocol,
+                                 const QByteArray &cookie, quint32 screenNumber);
+    void sendExecPacket(quint32 remoteChannel, const QByteArray &command);
+    void sendShellPacket(quint32 remoteChannel);
+    void sendSftpPacket(quint32 remoteChannel);
+    void sendWindowAdjustPacket(quint32 remoteChannel, quint32 bytesToAdd);
+    void sendChannelDataPacket(quint32 remoteChannel, const QByteArray &data);
+    void sendChannelSignalPacket(quint32 remoteChannel,
+        const QByteArray &signalName);
+    void sendChannelEofPacket(quint32 remoteChannel);
+    void sendChannelClosePacket(quint32 remoteChannel);
+    void sendChannelOpenConfirmationPacket(quint32 remoteChannel, quint32 localChannel,
+        quint32 localWindowSize, quint32 maxPackeSize);
+    void sendChannelOpenFailurePacket(quint32 remoteChannel, quint32 reason,
+        const QByteArray &reasonString);
+    quint32 nextClientSeqNr() const { return m_clientSeqNr; }
+
+    bool encrypterIsValid() const { return m_encrypter.isValid(); }
+
+private:
+    void sendPacket();
+
+    quint32 m_clientSeqNr;
+    SshEncryptionFacility m_encrypter;
+    QTcpSocket *m_socket;
+    SshOutgoingPacket m_outgoingPacket;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHCONNECTIONOUTSTATE_P_H

+ 137 - 0
src/tool/serverctrl/qssh/sshtcpipforwardserver.cpp

@@ -0,0 +1,137 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "sshtcpipforwardserver.h"
+#include "sshtcpipforwardserver_p.h"
+
+#include "sshlogging_p.h"
+#include "sshsendfacility_p.h"
+
+namespace QSsh {
+namespace Internal {
+
+SshTcpIpForwardServerPrivate::SshTcpIpForwardServerPrivate(const QString &bindAddress,
+        quint16 bindPort, SshSendFacility &sendFacility)
+    : m_sendFacility(sendFacility),
+      m_bindAddress(bindAddress),
+      m_bindPort(bindPort),
+      m_state(SshTcpIpForwardServer::Inactive)
+{
+}
+
+} // namespace Internal
+
+using namespace Internal;
+
+SshTcpIpForwardServer::SshTcpIpForwardServer(const QString &bindAddress, quint16 bindPort,
+                                             SshSendFacility &sendFacility)
+    : d(new SshTcpIpForwardServerPrivate(bindAddress, bindPort, sendFacility))
+{
+    d->m_timeoutTimer.setTimerType(Qt::VeryCoarseTimer);
+    connect(&d->m_timeoutTimer, &QTimer::timeout, this, &SshTcpIpForwardServer::setClosed);
+}
+
+void SshTcpIpForwardServer::setListening(quint16 port)
+{
+    QSSH_ASSERT_AND_RETURN(d->m_state != Listening);
+    d->m_bindPort = port;
+    d->m_state = Listening;
+    emit stateChanged(Listening);
+}
+
+void SshTcpIpForwardServer::setClosed()
+{
+    QSSH_ASSERT_AND_RETURN(d->m_state != Inactive);
+    d->m_state = Inactive;
+    emit stateChanged(Inactive);
+}
+
+void SshTcpIpForwardServer::setNewConnection(const SshForwardedTcpIpTunnel::Ptr &connection)
+{
+    d->m_pendingConnections.append(connection);
+    emit newConnection();
+}
+
+SshTcpIpForwardServer::~SshTcpIpForwardServer()
+{
+    delete d;
+}
+
+void SshTcpIpForwardServer::initialize()
+{
+    if (d->m_state == Inactive || d->m_state == Closing) {
+        try {
+            d->m_state = Initializing;
+            emit stateChanged(Initializing);
+            d->m_sendFacility.sendTcpIpForwardPacket(d->m_bindAddress.toUtf8(), d->m_bindPort);
+            d->m_timeoutTimer.start(SshTcpIpForwardServerPrivate::ReplyTimeout);
+        } catch (const std::exception &e) {
+            qCWarning(sshLog, "Botan error: %s", e.what());
+            d->m_timeoutTimer.stop();
+            setClosed();
+        }
+    }
+}
+
+void SshTcpIpForwardServer::close()
+{
+    d->m_timeoutTimer.stop();
+
+    if (d->m_state == Initializing || d->m_state == Listening) {
+        try {
+            d->m_state = Closing;
+            emit stateChanged(Closing);
+            d->m_sendFacility.sendCancelTcpIpForwardPacket(d->m_bindAddress.toUtf8(),
+                                                           d->m_bindPort);
+            d->m_timeoutTimer.start(SshTcpIpForwardServerPrivate::ReplyTimeout);
+        } catch (const std::exception &e) {
+            qCWarning(sshLog, "Botan error: %s", e.what());
+            d->m_timeoutTimer.stop();
+            setClosed();
+        }
+    }
+}
+
+const QString &SshTcpIpForwardServer::bindAddress() const
+{
+    return d->m_bindAddress;
+}
+
+quint16 SshTcpIpForwardServer::port() const
+{
+    return d->m_bindPort;
+}
+
+SshTcpIpForwardServer::State SshTcpIpForwardServer::state() const
+{
+    return d->m_state;
+}
+
+SshForwardedTcpIpTunnel::Ptr SshTcpIpForwardServer::nextPendingConnection()
+{
+    return d->m_pendingConnections.takeFirst();
+}
+
+} // namespace QSsh

+ 81 - 0
src/tool/serverctrl/qssh/sshtcpipforwardserver.h

@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "ssh_global.h"
+#include "sshforwardedtcpiptunnel.h"
+#include <QObject>
+
+namespace QSsh {
+
+namespace Internal {
+class SshChannelManager;
+class SshTcpIpForwardServerPrivate;
+class SshSendFacility;
+class SshConnectionPrivate;
+} // namespace Internal
+
+class QSSH_EXPORT SshTcpIpForwardServer : public QObject
+{
+    Q_OBJECT
+    friend class Internal::SshChannelManager;
+    friend class Internal::SshConnectionPrivate;
+
+public:
+    enum State {
+        Inactive,
+        Initializing,
+        Listening,
+        Closing
+    };
+
+    typedef QSharedPointer<SshTcpIpForwardServer> Ptr;
+    ~SshTcpIpForwardServer();
+
+    const QString &bindAddress() const;
+    quint16 port() const;
+    State state() const;
+    void initialize();
+    void close();
+
+    SshForwardedTcpIpTunnel::Ptr nextPendingConnection();
+
+signals:
+    void error(const QString &reason);
+    void newConnection();
+    void stateChanged(State state);
+
+private:
+    SshTcpIpForwardServer(const QString &bindAddress, quint16 bindPort,
+                          Internal::SshSendFacility &sendFacility);
+    void setListening(quint16 port);
+    void setClosed();
+    void setNewConnection(const SshForwardedTcpIpTunnel::Ptr &connection);
+
+    Internal::SshTcpIpForwardServerPrivate * const d;
+};
+
+} // namespace QSsh

+ 54 - 0
src/tool/serverctrl/qssh/sshtcpipforwardserver_p.h

@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "sshtcpipforwardserver.h"
+#include <QList>
+#include <QTimer>
+
+namespace QSsh {
+namespace Internal {
+
+class SshTcpIpForwardServerPrivate
+{
+public:
+    static const int ReplyTimeout = 10000; // milli seconds
+
+    SshTcpIpForwardServerPrivate(const QString &bindAddress, quint16 bindPort,
+                                 SshSendFacility &sendFacility);
+
+    SshSendFacility &m_sendFacility;
+    QTimer m_timeoutTimer;
+
+    const QString m_bindAddress;
+    quint16 m_bindPort;
+    SshTcpIpForwardServer::State m_state;
+
+    QList<SshForwardedTcpIpTunnel::Ptr> m_pendingConnections;
+};
+
+} // namespace Internal
+} // namespace QSsh

+ 123 - 0
src/tool/serverctrl/qssh/sshtcpiptunnel.cpp

@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "sshsendfacility_p.h"
+#include "sshtcpiptunnel_p.h"
+
+#include "sshincomingpacket_p.h"
+#include "sshexception_p.h"
+#include "sshlogging_p.h"
+
+namespace QSsh {
+
+namespace Internal {
+SshTcpIpTunnelPrivate::SshTcpIpTunnelPrivate(quint32 channelId, SshSendFacility &sendFacility)
+    : AbstractSshChannel(channelId, sendFacility)
+{
+    connect(this, &AbstractSshChannel::eof, this, &SshTcpIpTunnelPrivate::handleEof);
+}
+
+SshTcpIpTunnelPrivate::~SshTcpIpTunnelPrivate()
+{
+    closeChannel();
+}
+
+
+
+void SshTcpIpTunnelPrivate::handleChannelSuccess()
+{
+    throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_MSG_CHANNEL_SUCCESS message.");
+}
+
+void SshTcpIpTunnelPrivate::handleChannelFailure()
+{
+    throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+            "Unexpected SSH_MSG_CHANNEL_FAILURE message.");
+}
+
+void SshTcpIpTunnelPrivate::handleOpenFailureInternal(const QString &reason)
+{
+    emit error(reason);
+    closeChannel();
+}
+
+void SshTcpIpTunnelPrivate::handleChannelDataInternal(const QByteArray &data)
+{
+    m_data += data;
+    emit readyRead();
+}
+
+void SshTcpIpTunnelPrivate::handleChannelExtendedDataInternal(quint32 type,
+                                                                           const QByteArray &data)
+{
+    qCWarning(sshLog, "%s: Unexpected extended channel data. Type is %u, content is '%s'.",
+              Q_FUNC_INFO, type, data.constData());
+}
+
+void SshTcpIpTunnelPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
+{
+    qCWarning(sshLog, "%s: Unexpected exit status %d.", Q_FUNC_INFO, exitStatus.exitStatus);
+}
+
+void SshTcpIpTunnelPrivate::handleExitSignal(const SshChannelExitSignal &signal)
+{
+    qCWarning(sshLog, "%s: Unexpected exit signal %s.", Q_FUNC_INFO, signal.signal.constData());
+}
+
+void SshTcpIpTunnelPrivate::closeHook()
+{
+    emit closed();
+}
+
+void SshTcpIpTunnelPrivate::handleEof()
+{
+    /*
+     * For some reason, the OpenSSH server only sends EOF when the remote port goes away,
+     * but does not close the channel, even though it becomes useless in that case.
+     * So we close it ourselves.
+     */
+    closeChannel();
+}
+
+qint64 SshTcpIpTunnelPrivate::readData(char *data, qint64 maxlen)
+{
+    const qint64 bytesRead = qMin(qint64(m_data.count()), maxlen);
+    memcpy(data, m_data.constData(), bytesRead);
+    m_data.remove(0, bytesRead);
+    return bytesRead;
+}
+
+qint64 SshTcpIpTunnelPrivate::writeData(const char *data, qint64 len)
+{
+    QSSH_ASSERT_AND_RETURN_VALUE(channelState() == AbstractSshChannel::SessionEstablished, 0);
+
+    sendData(QByteArray(data, len));
+    return len;
+}
+
+} // namespace Internal
+
+} // namespace QSsh

+ 82 - 0
src/tool/serverctrl/qssh/sshtcpiptunnel_p.h

@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "sshchannel_p.h"
+#include <QIODevice>
+#include <QByteArray>
+
+namespace QSsh {
+namespace Internal {
+
+class SshTcpIpTunnelPrivate : public AbstractSshChannel
+{
+    Q_OBJECT
+
+public:
+    SshTcpIpTunnelPrivate(quint32 channelId, SshSendFacility &sendFacility);
+    ~SshTcpIpTunnelPrivate();
+
+    template<class SshTcpIpTunnel>
+    void init(SshTcpIpTunnel *q)
+    {
+        connect(this, &SshTcpIpTunnelPrivate::closed,
+                q, &SshTcpIpTunnel::close, Qt::QueuedConnection);
+        connect(this, &SshTcpIpTunnelPrivate::readyRead,
+                q, &SshTcpIpTunnel::readyRead, Qt::QueuedConnection);
+        connect(this, &SshTcpIpTunnelPrivate::error, q, [q](const QString &reason) {
+            q->setErrorString(reason);
+            emit q->error(reason);
+        }, Qt::QueuedConnection);
+    }
+
+    void handleChannelSuccess() override;
+    void handleChannelFailure() override;
+
+    qint64 readData(char *data, qint64 maxlen);
+    qint64 writeData(const char *data, qint64 len);
+
+signals:
+    void readyRead();
+    void error(const QString &reason);
+    void closed();
+
+protected:
+    void handleOpenFailureInternal(const QString &reason) override;
+    void handleChannelDataInternal(const QByteArray &data) override;
+    void handleChannelExtendedDataInternal(quint32 type, const QByteArray &data) override;
+    void handleExitStatus(const SshChannelExitStatus &exitStatus) override;
+    void handleExitSignal(const SshChannelExitSignal &signal) override;
+    void closeHook() override;
+
+    QByteArray m_data;
+
+private:
+    void handleEof();
+};
+
+} // namespace Internal
+} // namespace QSsh

+ 222 - 0
src/tool/serverctrl/qssh/sshx11channel.cpp

@@ -0,0 +1,222 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "sshx11channel_p.h"
+
+#include "sshincomingpacket_p.h"
+#include "sshlogging_p.h"
+#include "sshsendfacility_p.h"
+
+#include <QFileInfo>
+#include <QLocalSocket>
+#include <QTcpSocket>
+
+namespace QSsh {
+namespace Internal {
+
+class X11Socket : public QObject
+{
+    Q_OBJECT
+public:
+    X11Socket(QObject *parent) : QObject(parent) { }
+
+    void establishConnection(const X11DisplayInfo &displayInfo)
+    {
+        const bool hostNameIsPath = displayInfo.hostName.startsWith(QLatin1Char('/')); // macOS
+        const bool hasActualHostName = !displayInfo.hostName.isEmpty()
+                && displayInfo.hostName != QLatin1String("unix") && !displayInfo.hostName.endsWith(QLatin1String("/unix"))
+                && !hostNameIsPath;
+        if (hasActualHostName) {
+            QTcpSocket * const socket = new QTcpSocket(this);
+            connect(socket, &QTcpSocket::connected, this, &X11Socket::connected);
+//            connect(socket, &QTcpSocket::errorOccurred, this, [this, socket] {
+//                emit error(socket->errorString());
+//            });
+            socket->connectToHost(displayInfo.hostName, 6000 + displayInfo.display);
+            m_socket = socket;
+        } else {
+            const QString serverBasePath = hostNameIsPath ? QString(displayInfo.hostName + QLatin1Char(':'))
+                                                          : QLatin1String("/tmp/.X11-unix/X");
+            QLocalSocket * const socket = new QLocalSocket(this);
+            connect(socket, &QLocalSocket::connected, this, &X11Socket::connected);
+//            connect(socket, SIGNAL(error()), this, [this, socket] {
+//                emit error(socket->errorString());
+//            });
+            socket->connectToServer(serverBasePath + QString::number(displayInfo.display));
+            m_socket = socket;
+        }
+        connect(m_socket, &QIODevice::readyRead, this,
+                [this] { emit dataAvailable(m_socket->readAll()); });
+    }
+
+    void closeConnection()
+    {
+        m_socket->disconnect();
+        if (localSocket())
+            localSocket()->disconnectFromServer();
+        else
+            tcpSocket()->disconnectFromHost();
+    }
+
+    void write(const QByteArray &data)
+    {
+        m_socket->write(data);
+    }
+
+    bool hasError() const
+    {
+        return (localSocket() && localSocket()->error() != QLocalSocket::UnknownSocketError)
+                || (tcpSocket() && tcpSocket()->error() != QTcpSocket::UnknownSocketError);
+    }
+
+    bool isConnected() const
+    {
+        return (localSocket() && localSocket()->state() == QLocalSocket::ConnectedState)
+                || (tcpSocket() && tcpSocket()->state() == QTcpSocket::ConnectedState);
+    }
+
+signals:
+    void connected();
+    void error(const QString &message);
+    void dataAvailable(const QByteArray &data);
+
+private:
+    QLocalSocket *localSocket() const { return qobject_cast<QLocalSocket *>(m_socket); }
+    QTcpSocket *tcpSocket() const { return qobject_cast<QTcpSocket *>(m_socket); }
+
+    QIODevice *m_socket = nullptr;
+};
+
+SshX11Channel::SshX11Channel(const X11DisplayInfo &displayInfo, quint32 channelId,
+                             SshSendFacility &sendFacility)
+    : AbstractSshChannel (channelId, sendFacility),
+      m_x11Socket(new X11Socket(this)),
+      m_displayInfo(displayInfo)
+{
+    setChannelState(SessionRequested); // Invariant for parent class.
+}
+
+void SshX11Channel::handleChannelSuccess()
+{
+    qCWarning(sshLog) << "unexpected channel success message for X11 channel";
+}
+
+void SshX11Channel::handleChannelFailure()
+{
+    qCWarning(sshLog) << "unexpected channel failure message for X11 channel";
+}
+
+void SshX11Channel::handleOpenSuccessInternal()
+{
+    m_sendFacility.sendChannelOpenConfirmationPacket(remoteChannel(), localChannelId(),
+                                                     initialWindowSize(), maxPacketSize());
+    connect(m_x11Socket, &X11Socket::connected, this,
+        [this] {
+            qCDebug(sshLog) << "x11 socket connected for channel" << localChannelId();
+            if (!m_queuedRemoteData.isEmpty())
+                handleRemoteData(QByteArray());
+        }
+    );
+    connect(m_x11Socket, &X11Socket::error, this,
+        [this](const QString &msg) {
+            emit error(tr("X11 socket error: %1").arg(msg));
+        }
+    );
+    connect(m_x11Socket, &X11Socket::dataAvailable, this,
+        [this](const QByteArray &data) {
+            qCDebug(sshLog) << "sending " << data.size() << "bytes from x11 socket to remote side "
+                               "in channel" << localChannelId();
+            sendData(data);
+    });
+    m_x11Socket->establishConnection(m_displayInfo);
+}
+
+void SshX11Channel::handleOpenFailureInternal(const QString &reason)
+{
+    qCWarning(sshLog) << "unexpected channel open failure message for X11 channel:" << reason;
+}
+
+void SshX11Channel::handleChannelDataInternal(const QByteArray &data)
+{
+    handleRemoteData(data);
+}
+
+void SshX11Channel::handleChannelExtendedDataInternal(quint32 type, const QByteArray &data)
+{
+    qCWarning(sshLog) << "unexpected extended data for X11 channel" << type << data;
+}
+
+void SshX11Channel::handleExitStatus(const SshChannelExitStatus &exitStatus)
+{
+    qCWarning(sshLog) << "unexpected exit status message on X11 channel" << exitStatus.exitStatus;
+    closeChannel();
+}
+
+void SshX11Channel::handleExitSignal(const SshChannelExitSignal &signal)
+{
+    qCWarning(sshLog) << "unexpected exit signal message on X11 channel" << signal.error;
+    closeChannel();
+}
+
+void SshX11Channel::closeHook()
+{
+    m_x11Socket->disconnect();
+    m_x11Socket->closeConnection();
+}
+
+void SshX11Channel::handleRemoteData(const QByteArray &data)
+{
+    if (m_x11Socket->hasError())
+        return;
+    qCDebug(sshLog) << "received" << data.size() << "bytes from remote side in x11 channel"
+                    << localChannelId();
+    if (!m_x11Socket->isConnected()) {
+        qCDebug(sshLog) << "x11 socket not yet connected, queueing data";
+        m_queuedRemoteData += data;
+        return;
+    }
+    if (m_haveReplacedRandomCookie) {
+        qCDebug(sshLog) << "forwarding data to x11 socket";
+        m_x11Socket->write(data);
+        return;
+    }
+    m_queuedRemoteData += data;
+    const int randomCookieOffset = m_queuedRemoteData.indexOf(m_displayInfo.randomCookie);
+    if (randomCookieOffset == -1) {
+        qCDebug(sshLog) << "random cookie has not appeared in remote data yet, queueing data";
+        return;
+    }
+    m_queuedRemoteData.replace(randomCookieOffset, m_displayInfo.cookie.size(),
+                               m_displayInfo.cookie);
+    qCDebug(sshLog) << "found and replaced random cookie, forwarding data to x11 socket";
+    m_x11Socket->write(m_queuedRemoteData);
+    m_queuedRemoteData.clear();
+    m_haveReplacedRandomCookie = true;
+}
+
+} // namespace Internal
+} // namespace QSsh
+
+#include <sshx11channel.moc>

+ 73 - 0
src/tool/serverctrl/qssh/sshx11channel_p.h

@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "sshchannel_p.h"
+
+#include "sshx11displayinfo_p.h"
+
+#include <QByteArray>
+
+namespace QSsh {
+namespace Internal {
+class SshChannelManager;
+class SshSendFacility;
+class X11Socket;
+
+class SshX11Channel : public AbstractSshChannel
+{
+    Q_OBJECT
+
+    friend class Internal::SshChannelManager;
+
+signals:
+    void error(const QString &message);
+
+private:
+    SshX11Channel(const X11DisplayInfo &displayInfo, quint32 channelId,
+                  SshSendFacility &sendFacility);
+
+    void handleChannelSuccess() override;
+    void handleChannelFailure() override;
+
+    void handleOpenSuccessInternal() override;
+    void handleOpenFailureInternal(const QString &reason) override;
+    void handleChannelDataInternal(const QByteArray &data) override;
+    void handleChannelExtendedDataInternal(quint32 type, const QByteArray &data) override;
+    void handleExitStatus(const SshChannelExitStatus &exitStatus) override;
+    void handleExitSignal(const SshChannelExitSignal &signal) override;
+    void closeHook() override;
+
+    void handleRemoteData(const QByteArray &data);
+
+    X11Socket * const m_x11Socket;
+    const X11DisplayInfo m_displayInfo;
+    QByteArray m_queuedRemoteData;
+    bool m_haveReplacedRandomCookie = false;
+};
+
+} // namespace Internal
+} // namespace QSsh

+ 49 - 0
src/tool/serverctrl/qssh/sshx11displayinfo_p.h

@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "ssh_global.h"
+
+#include <QString>
+#include <QByteArray>
+
+namespace QSsh {
+namespace Internal {
+
+class QSSH_EXPORT X11DisplayInfo
+{
+public:
+    QString displayName;
+    QString hostName;
+    QByteArray protocol;
+    QByteArray cookie;
+    QByteArray randomCookie;
+    int display = 0;
+    int screen = 0;
+};
+
+} // namespace Internal
+} // namespace QSsh

+ 161 - 0
src/tool/serverctrl/qssh/sshx11inforetriever.cpp

@@ -0,0 +1,161 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "sshx11inforetriever_p.h"
+
+#include "sshlogging_p.h"
+#include "sshx11displayinfo_p.h"
+
+#include <QByteArrayList>
+#include <QProcess>
+#include <QTemporaryFile>
+#include <QTimer>
+
+#include <botan/auto_rng.h>
+
+namespace QSsh {
+namespace Internal {
+
+static QByteArray xauthProtocol() { return "MIT-MAGIC-COOKIE-1"; }
+
+SshX11InfoRetriever::SshX11InfoRetriever(const QString &displayName, QObject *parent)
+    : QObject(parent),
+      m_displayName(displayName),
+      m_xauthProc(new QProcess(this)),
+      m_xauthFile(new QTemporaryFile(this))
+{
+    connect(m_xauthProc, &QProcess::errorOccurred, this,
+        [this] {
+            if (m_xauthProc->error() == QProcess::FailedToStart) {
+                emitFailure(tr("Could not start xauth: %1").arg(m_xauthProc->errorString()));
+            }
+        }
+    );
+    connect(m_xauthProc, static_cast<void (QProcess::*)(int,QProcess::ExitStatus)>(&QProcess::finished), this,
+        [this] {
+            if (m_xauthProc->exitStatus() != QProcess::NormalExit) {
+                emitFailure(tr("xauth crashed: %1").arg(m_xauthProc->errorString()));
+                return;
+            }
+            if (m_xauthProc->exitCode() != 0) {
+                emitFailure(tr("xauth failed with exit code %1.").arg(m_xauthProc->exitCode()));
+                return;
+            }
+            switch (m_state) {
+            case State::RunningGenerate:
+                m_state = State::RunningList;
+                m_xauthProc->start(QStringLiteral("xauth"), QStringList{
+                                       QStringLiteral("-f"),
+                                       m_xauthFile->fileName(),
+                                       QStringLiteral("list"),
+                                       m_displayName});
+                break;
+            case State::RunningList: {
+                const QByteArrayList outputLines = m_xauthProc->readAllStandardOutput().split('\n');
+                if (outputLines.empty()) {
+                    emitFailure(tr("Unexpected xauth output."));
+                    return;
+                }
+                const QByteArrayList data = outputLines.first().simplified().split(' ');
+                if (data.size() < 3 || data.at(1) != xauthProtocol() || data.at(2).isEmpty()) {
+                    emitFailure(tr("Unexpected xauth output."));
+                    return;
+                }
+                X11DisplayInfo displayInfo;
+                displayInfo.displayName = m_displayName;
+                const int colonIndex = m_displayName.indexOf(QLatin1Char(':'));
+                if (colonIndex == -1) {
+                    emitFailure(tr("Invalid display name \"%1\"").arg(m_displayName));
+                    return;
+                }
+                displayInfo.hostName = m_displayName.mid(0, colonIndex);
+                const int dotIndex = m_displayName.indexOf(QLatin1Char('.'), colonIndex + 1);
+                const QString display = m_displayName.mid(colonIndex + 1,
+                                                          dotIndex == -1 ? -1
+                                                                         : dotIndex - colonIndex - 1);
+                if (display.isEmpty()) {
+                    emitFailure(tr("Invalid display name \"%1\"").arg(m_displayName));
+                    return;
+                }
+                bool ok;
+                displayInfo.display = display.toInt(&ok);
+                if (!ok) {
+                    emitFailure(tr("Invalid display name \"%1\"").arg(m_displayName));
+                    return;
+                }
+                if (dotIndex != -1) {
+                    displayInfo.screen = m_displayName.mid(dotIndex + 1).toInt(&ok);
+                    if (!ok) {
+                        emitFailure(tr("Invalid display name \"%1\"").arg(m_displayName));
+                        return;
+                    }
+                }
+                displayInfo.protocol = data.at(1);
+                displayInfo.cookie = QByteArray::fromHex(data.at(2));
+                displayInfo.randomCookie.resize(displayInfo.cookie.size());
+                try {
+                    Botan::AutoSeeded_RNG rng;
+                    rng.randomize(reinterpret_cast<Botan::uint8_t *>(displayInfo.randomCookie.data()),
+                                  displayInfo.randomCookie.size());
+                } catch (const std::exception &ex) {
+                    emitFailure(tr("Failed to generate random cookie: %1")
+                                .arg(QLatin1String(ex.what())));
+                    return;
+                }
+                emit success(displayInfo);
+                deleteLater();
+                break;
+            }
+            default:
+                emitFailure(tr("Internal error"));
+            }
+        }
+    );
+}
+
+void SshX11InfoRetriever::start()
+{
+    if (!m_xauthFile->open()) {
+        emitFailure(tr("Could not create temporary file: %1").arg(m_xauthFile->errorString()));
+        return;
+    }
+    m_state = State::RunningGenerate;
+    m_xauthProc->start(QStringLiteral("xauth"), QStringList{
+                           QStringLiteral("-f"),
+                           m_xauthFile->fileName(),
+                           QStringLiteral("generate"),
+                           m_displayName, QString::fromLatin1(xauthProtocol())});
+}
+
+void SshX11InfoRetriever::emitFailure(const QString &reason)
+{
+    QTimer::singleShot(0, this, [this, reason] {
+        emit failure(tr("Could not retrieve X11 authentication cookie: %1").arg(reason));
+        deleteLater();
+    });
+}
+
+} // namespace Internal
+} // namespace QSsh

+ 65 - 0
src/tool/serverctrl/qssh/sshx11inforetriever_p.h

@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "ssh_global.h"
+
+#include <QObject>
+#include <QString>
+
+QT_BEGIN_NAMESPACE
+class QByteArray;
+class QProcess;
+class QTemporaryFile;
+QT_END_NAMESPACE
+
+namespace QSsh {
+namespace Internal {
+class X11DisplayInfo;
+
+class QSSH_EXPORT SshX11InfoRetriever : public QObject
+{
+    Q_OBJECT
+public:
+    SshX11InfoRetriever(const QString &displayName, QObject *parent = nullptr);
+    void start();
+
+signals:
+    void failure(const QString &message);
+    void success(const X11DisplayInfo &displayInfo);
+
+private:
+    void emitFailure(const QString &reason);
+
+    const QString m_displayName;
+    QProcess * const m_xauthProc;
+    QTemporaryFile * const m_xauthFile;
+
+    enum class State { Inactive, RunningGenerate, RunningList } m_state = State::Inactive;
+};
+
+} // namespace Internal
+} // namespace QSsh

+ 140 - 0
src/tool/serverctrl/remoteprocess.cpp

@@ -0,0 +1,140 @@
+#include "remoteprocess.h"
+
+
+#include <iostream>
+#include <thread>
+
+using namespace QSsh;
+
+const QByteArray StderrOutput("ChannelTest");
+
+RemoteProcess::RemoteProcess(const QSsh::SshConnectionParameters &params)
+    : m_sshParams(params),
+      m_timeoutTimer(new QTimer(this)),
+      m_sshConnection(0),
+      m_remoteRunner(new SshRemoteProcessRunner(this)),
+      m_state(Inactive)
+{
+
+    connect(m_remoteRunner, SIGNAL(connectionError()),
+        SLOT(handleConnectionError()));
+    connect(m_remoteRunner, SIGNAL(processStarted()),
+        SLOT(handleProcessStarted()));
+    connect(m_remoteRunner, SIGNAL(readyReadStandardOutput()), SLOT(handleProcessStdout()));
+    connect(m_remoteRunner, SIGNAL(readyReadStandardError()), SLOT(handleProcessStderr()));
+    connect(m_remoteRunner, SIGNAL(processClosed(int)),
+        SLOT(handleProcessClosed(int)));
+    m_started = false;
+
+ //   m_remoteRunner->run("ls -a /tmp", m_sshParams);
+}
+
+RemoteProcess::~RemoteProcess()
+{
+    if(m_started)
+    {
+        delete m_remoteRunner;
+
+    }
+}
+
+void RemoteProcess::run()
+{
+
+
+//    std::cout << "Testing successful remote process... " << std::flush;
+//    m_state = TestingSuccess;
+
+    mbQueryState = true;
+//    m_timeoutTimer->start();
+    m_remoteRunner->run("ps -ef | grep driver_", m_sshParams);
+}
+
+void RemoteProcess::runCmd(QString strcmd)
+{
+    QByteArray ba(strcmd.toLatin1().data());
+    m_remoteRunner->run(ba, m_sshParams);
+}
+
+void RemoteProcess::handleConnectionError()
+{
+    const QString error = m_state == TestingIoDevice || m_state == TestingProcessChannels
+        ? m_sshConnection->errorString() : m_remoteRunner->lastConnectionErrorString();
+
+    std::cerr << "Error: Connection failure (" << qPrintable(error) << ")." << std::endl;
+}
+
+void RemoteProcess::handleProcessStarted()
+{
+    if (m_started) {
+ //       std::cerr << "Error: Received started() signal again." << std::endl;
+    } else {
+        m_started = true;
+        std::cout<<" connected."<<std::endl;
+    }
+}
+
+void RemoteProcess::handleProcessStdout()
+{
+    if (!m_started) {
+        std::cerr << "Error: Remote output from non-started process."
+            << std::endl;
+ //       QCoreApplication::exit(EXIT_FAILURE);
+    }
+    else {
+        m_remoteStdout = m_remoteRunner->readAllStandardOutput();
+        std::cout<<" out: "<<m_remoteStdout.data()<<std::endl;
+        if(mbQueryState)
+        {
+            mbQueryState = false;
+            QString str = m_remoteStdout;
+            bool brm = false;
+            bool bgr = false;
+            int nState = 0;
+            if(str.indexOf("/driver_cloud_grpc_server_h264")>=0)
+            {
+                brm = true;
+
+            }
+            if(str.indexOf("/driver_group_grpc_server")>=0)
+            {
+                bgr = true;
+            }
+            if(brm&&bgr)
+            {
+                nState = 3;
+            }
+            else
+            {
+                if(brm)
+                {
+                    nState = 1;
+                }
+                else
+                {
+                    if(bgr)
+                        nState = 2;
+                }
+            }
+
+            emit ServiceState(nState);
+        }
+    }
+}
+
+void RemoteProcess::handleProcessStderr()
+{
+    if (!m_started) {
+        std::cerr << "Error: Remote error output from non-started process."
+            << std::endl;
+
+    } else {
+        m_remoteStderr = m_remoteRunner->readAllStandardError();
+        std::cout<<"error: "<<m_remoteStderr.data()<<std::endl;
+    }
+}
+
+void RemoteProcess::handleProcessClosed(int exitStatus)
+{
+    std::cout<<" process close. "<<std::endl;
+}

+ 66 - 0
src/tool/serverctrl/remoteprocess.h

@@ -0,0 +1,66 @@
+#ifndef REMOTEPROCESS_H
+#define REMOTEPROCESS_H
+
+#include <QObject>
+
+#include <qssh/sshremoteprocessrunner.h>
+
+#include <QObject>
+#include <QScopedPointer>
+#include <QTimer>
+
+QT_FORWARD_DECLARE_CLASS(QTextStream)
+QT_FORWARD_DECLARE_CLASS(QTimer)
+
+class RemoteProcess : public QObject
+{
+    Q_OBJECT
+public:
+    RemoteProcess(const QSsh::SshConnectionParameters &params);
+    ~RemoteProcess();
+
+    void run();
+
+    void runCmd(QString strcmd);
+
+signals:
+    void ServiceState(int nState);
+
+
+private slots:
+    void handleConnectionError();
+    void handleProcessStarted();
+    void handleProcessStdout();
+    void handleProcessStderr();
+    void handleProcessClosed(int exitStatus);
+//    void handleTimeout();
+//    void handleReadyRead();
+//    void handleReadyReadStdout();
+//    void handleReadyReadStderr();
+//    void handleConnected();
+
+
+private:
+    enum State {
+        Inactive, TestingSuccess, TestingFailure, TestingCrash, TestingTerminal, TestingIoDevice,
+        TestingProcessChannels
+    };
+
+    const QSsh::SshConnectionParameters m_sshParams;
+    QTimer * const m_timeoutTimer;
+    QScopedPointer<QTextStream> m_textStream;
+    QSsh::SshConnection *m_sshConnection;
+    QSsh::SshRemoteProcessRunner * const m_remoteRunner;
+    QSsh::SshRemoteProcess::Ptr m_catProcess;
+    QSsh::SshRemoteProcess::Ptr m_echoProcess;
+    QByteArray m_remoteStdout;
+    QByteArray m_remoteStderr;
+    QByteArray m_remoteData;
+    State m_state;
+    bool m_started;
+
+    bool mbQueryState = false;
+
+};
+
+#endif // REMOTEPROCESS_H

+ 36 - 0
src/tool/serverctrl/serverctrl.pro

@@ -0,0 +1,36 @@
+QT       += core gui network
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+CONFIG += c++11
+
+# The following define makes your compiler emit warnings if you use
+# any Qt feature that has been marked deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+    main.cpp \
+    mainwindow.cpp \
+    remoteprocess.cpp
+
+HEADERS += \
+    mainwindow.h \
+    remoteprocess.h
+
+FORMS += \
+    mainwindow.ui
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
+
+
+LIBS +=  -L$$PWD -lQSsh