summaryrefslogtreecommitdiff
path: root/src/plugins
diff options
context:
space:
mode:
authorck <qt-info@nokia.com>2010-07-12 09:33:22 +0200
committerck <qt-info@nokia.com>2010-07-12 09:33:22 +0200
commit65eb1d551592421f5e0e961c8c1dd2d056023697 (patch)
treee4698e3ee36443d67911ff0de3b9637aa0917484 /src/plugins
parente43287fb3a4e2b69ada07455760aa30afb7eb10c (diff)
downloadqt-creator-65eb1d551592421f5e0e961c8c1dd2d056023697.tar.gz
Replace SSH library.
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/coreplugin/coreplugin.cpp3
-rw-r--r--src/plugins/coreplugin/coreplugin.pro52
-rw-r--r--src/plugins/coreplugin/coreplugin_dependencies.pri2
-rw-r--r--src/plugins/coreplugin/ssh/ne7sshobject.cpp81
-rw-r--r--src/plugins/coreplugin/ssh/ne7sshobject.h80
-rw-r--r--src/plugins/coreplugin/ssh/sftpchannel.cpp756
-rw-r--r--src/plugins/coreplugin/ssh/sftpchannel.h120
-rw-r--r--src/plugins/coreplugin/ssh/sftpchannel_p.h130
-rw-r--r--src/plugins/coreplugin/ssh/sftpdefs.cpp32
-rw-r--r--src/plugins/coreplugin/ssh/sftpdefs.h48
-rw-r--r--src/plugins/coreplugin/ssh/sftpincomingpacket.cpp230
-rw-r--r--src/plugins/coreplugin/ssh/sftpincomingpacket_p.h111
-rw-r--r--src/plugins/coreplugin/ssh/sftpoperation.cpp176
-rw-r--r--src/plugins/coreplugin/ssh/sftpoperation_p.h193
-rw-r--r--src/plugins/coreplugin/ssh/sftpoutgoingpacket.cpp202
-rw-r--r--src/plugins/coreplugin/ssh/sftpoutgoingpacket_p.h83
-rw-r--r--src/plugins/coreplugin/ssh/sftppacket.cpp54
-rw-r--r--src/plugins/coreplugin/ssh/sftppacket_p.h108
-rw-r--r--src/plugins/coreplugin/ssh/sshbotanconversions_p.h97
-rw-r--r--src/plugins/coreplugin/ssh/sshcapabilities.cpp103
-rw-r--r--src/plugins/coreplugin/ssh/sshcapabilities_p.h72
-rw-r--r--src/plugins/coreplugin/ssh/sshchannel.cpp244
-rw-r--r--src/plugins/coreplugin/ssh/sshchannel_p.h111
-rw-r--r--src/plugins/coreplugin/ssh/sshchannelmanager.cpp188
-rw-r--r--src/plugins/coreplugin/ssh/sshchannelmanager_p.h89
-rw-r--r--src/plugins/coreplugin/ssh/sshconnection.cpp768
-rw-r--r--src/plugins/coreplugin/ssh/sshconnection.h145
-rw-r--r--src/plugins/coreplugin/ssh/sshconnection_p.h157
-rw-r--r--src/plugins/coreplugin/ssh/sshcryptofacility.cpp369
-rw-r--r--src/plugins/coreplugin/ssh/sshcryptofacility_p.h154
-rw-r--r--src/plugins/coreplugin/ssh/sshdelayedsignal.cpp165
-rw-r--r--src/plugins/coreplugin/ssh/sshdelayedsignal_p.h190
-rw-r--r--src/plugins/coreplugin/ssh/ssherrors.h43
-rw-r--r--src/plugins/coreplugin/ssh/sshexception_p.h89
-rw-r--r--src/plugins/coreplugin/ssh/sshincomingpacket.cpp442
-rw-r--r--src/plugins/coreplugin/ssh/sshincomingpacket_p.h186
-rw-r--r--src/plugins/coreplugin/ssh/sshkeyexchange.cpp197
-rw-r--r--src/plugins/coreplugin/ssh/sshkeyexchange_p.h87
-rw-r--r--src/plugins/coreplugin/ssh/sshkeygenerator.cpp155
-rw-r--r--src/plugins/coreplugin/ssh/sshkeygenerator.h54
-rw-r--r--src/plugins/coreplugin/ssh/sshoutgoingpacket.cpp284
-rw-r--r--src/plugins/coreplugin/ssh/sshoutgoingpacket_p.h98
-rw-r--r--src/plugins/coreplugin/ssh/sshpacket.cpp167
-rw-r--r--src/plugins/coreplugin/ssh/sshpacket_p.h137
-rw-r--r--src/plugins/coreplugin/ssh/sshpacketparser.cpp153
-rw-r--r--src/plugins/coreplugin/ssh/sshpacketparser_p.h81
-rw-r--r--src/plugins/coreplugin/ssh/sshremoteprocess.cpp270
-rw-r--r--src/plugins/coreplugin/ssh/sshremoteprocess.h130
-rw-r--r--src/plugins/coreplugin/ssh/sshremoteprocess_p.h96
-rw-r--r--src/plugins/coreplugin/ssh/sshsendfacility.cpp191
-rw-r--r--src/plugins/coreplugin/ssh/sshsendfacility_p.h90
-rw-r--r--src/plugins/debugger/debuggerengine.h2
-rw-r--r--src/plugins/debugger/gdb/remotegdbprocess.cpp250
-rw-r--r--src/plugins/debugger/gdb/remotegdbprocess.h38
-rw-r--r--src/plugins/debugger/gdb/remoteplaingdbadapter.cpp4
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.cpp112
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.h18
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp4
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h2
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp530
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h74
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.cpp91
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.h13
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/maemosshconfigdialog.cpp18
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/maemosshthread.cpp215
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/maemosshthread.h132
-rw-r--r--src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri2
67 files changed, 8268 insertions, 1500 deletions
diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp
index 8e0107ed40..8c7e9bede3 100644
--- a/src/plugins/coreplugin/coreplugin.cpp
+++ b/src/plugins/coreplugin/coreplugin.cpp
@@ -34,7 +34,6 @@
#include "modemanager.h"
#include "fileiconprovider.h"
#include "designmode.h"
-#include "ssh/ne7sshobject.h"
#include <extensionsystem/pluginmanager.h>
@@ -89,7 +88,6 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
m_designMode = new DesignMode(editorManager);
addObject(m_designMode);
- Ne7SshObject::instance();
}
return success;
}
@@ -113,7 +111,6 @@ void CorePlugin::fileOpenRequest(const QString &f)
void CorePlugin::aboutToShutdown()
{
m_mainWindow->aboutToShutdown();
- Ne7SshObject::removeInstance();
}
Q_EXPORT_PLUGIN(CorePlugin)
diff --git a/src/plugins/coreplugin/coreplugin.pro b/src/plugins/coreplugin/coreplugin.pro
index 3b607d037b..ad81f39dc6 100644
--- a/src/plugins/coreplugin/coreplugin.pro
+++ b/src/plugins/coreplugin/coreplugin.pro
@@ -85,10 +85,27 @@ SOURCES += mainwindow.cpp \
editormanager/systemeditor.cpp \
designmode.cpp \
editortoolbar.cpp \
- ssh/ne7sshobject.cpp \
- ssh/sshconnection.cpp \
+ helpmanager.cpp \
+ ssh/sshsendfacility.cpp \
+ ssh/sshremoteprocess.cpp \
+ ssh/sshpacketparser.cpp \
+ ssh/sshpacket.cpp \
+ ssh/sshoutgoingpacket.cpp \
ssh/sshkeygenerator.cpp \
- helpmanager.cpp
+ ssh/sshkeyexchange.cpp \
+ ssh/sshincomingpacket.cpp \
+ ssh/sshcryptofacility.cpp \
+ ssh/sshconnection.cpp \
+ ssh/sshchannelmanager.cpp \
+ ssh/sshchannel.cpp \
+ ssh/sshcapabilities.cpp \
+ ssh/sftppacket.cpp \
+ ssh/sftpoutgoingpacket.cpp \
+ ssh/sftpoperation.cpp \
+ ssh/sftpincomingpacket.cpp \
+ ssh/sftpdefs.cpp \
+ ssh/sftpchannel.cpp \
+ ssh/sshdelayedsignal.cpp
HEADERS += mainwindow.h \
editmode.h \
@@ -171,10 +188,33 @@ HEADERS += mainwindow.h \
editormanager/systemeditor.h \
designmode.h \
editortoolbar.h \
- ssh/ne7sshobject.h \
- ssh/sshconnection.h \
+ helpmanager.h \
+ ssh/sshsendfacility_p.h \
+ ssh/sshremoteprocess.h \
+ ssh/sshremoteprocess_p.h \
+ ssh/sshpacketparser_p.h \
+ ssh/sshpacket_p.h \
+ ssh/sshoutgoingpacket_p.h \
ssh/sshkeygenerator.h \
- helpmanager.h
+ ssh/sshkeyexchange_p.h \
+ ssh/sshincomingpacket_p.h \
+ ssh/sshexception_p.h \
+ ssh/ssherrors.h \
+ ssh/sshcryptofacility_p.h \
+ ssh/sshconnection.h \
+ ssh/sshconnection_p.h \
+ ssh/sshchannelmanager_p.h \
+ ssh/sshchannel_p.h \
+ ssh/sshcapabilities_p.h \
+ ssh/sshbotanconversions_p.h \
+ ssh/sftppacket_p.h \
+ ssh/sftpoutgoingpacket_p.h \
+ ssh/sftpoperation_p.h \
+ ssh/sftpincomingpacket_p.h \
+ ssh/sftpdefs.h \
+ ssh/sftpchannel.h \
+ ssh/sftpchannel_p.h \
+ ssh/sshdelayedsignal_p.h
FORMS += dialogs/newdialog.ui \
actionmanager/commandmappings.ui \
diff --git a/src/plugins/coreplugin/coreplugin_dependencies.pri b/src/plugins/coreplugin/coreplugin_dependencies.pri
index 8726e1be08..e908601b22 100644
--- a/src/plugins/coreplugin/coreplugin_dependencies.pri
+++ b/src/plugins/coreplugin/coreplugin_dependencies.pri
@@ -1,3 +1,3 @@
include(../../libs/extensionsystem/extensionsystem.pri)
include(../../libs/utils/utils.pri)
-include(../../libs/3rdparty/net7ssh/net7ssh.pri)
+include(../../libs/3rdparty/botan/botan.pri)
diff --git a/src/plugins/coreplugin/ssh/ne7sshobject.cpp b/src/plugins/coreplugin/ssh/ne7sshobject.cpp
deleted file mode 100644
index 9f94a55b97..0000000000
--- a/src/plugins/coreplugin/ssh/ne7sshobject.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
-**
-** This file is part of Qt Creator.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** No Commercial Usage
-** This file contains pre-release code and may not be distributed.
-** You may use this file in accordance with the terms and conditions
-** contained in the Technology Preview License Agreement accompanying
-** this package.
-**
-** 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 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.
-**
-** If you have questions regarding the use of this file, please contact
-** Nokia at qt-info@nokia.com.
-**
-**
-**
-**
-**
-**
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "ne7sshobject.h"
-
-#include <QtCore/QMutexLocker>
-
-#include <ne7ssh.h>
-
-namespace Core {
-namespace Internal {
-
-Ne7SshObject *Ne7SshObject::instance()
-{
- if (!m_instance)
- m_instance = new Ne7SshObject;
- return m_instance;
-}
-
-void Ne7SshObject::removeInstance()
-{
- delete m_instance;
-}
-
-Ne7SshObject::Ptr Ne7SshObject::get()
-{
- QMutexLocker locker(&m_mutex);
- QSharedPointer<ne7ssh> shared = m_weakRef.toStrongRef();
- if (!shared) {
- shared = QSharedPointer<ne7ssh>(new ne7ssh);
- m_weakRef = shared;
- }
- return shared;
-}
-
-Ne7SshObject::Ne7SshObject()
-{
-}
-
-Ne7SshObject *Ne7SshObject::m_instance = 0;
-
-} // namespace Internal
-} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/ne7sshobject.h b/src/plugins/coreplugin/ssh/ne7sshobject.h
deleted file mode 100644
index 77d601ec86..0000000000
--- a/src/plugins/coreplugin/ssh/ne7sshobject.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
-**
-** This file is part of Qt Creator.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** No Commercial Usage
-** This file contains pre-release code and may not be distributed.
-** You may use this file in accordance with the terms and conditions
-** contained in the Technology Preview License Agreement accompanying
-** this package.
-**
-** 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 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.
-**
-** If you have questions regarding the use of this file, please contact
-** Nokia at qt-info@nokia.com.
-**
-**
-**
-**
-**
-**
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef NE7SSHOBJECT_H
-#define NE7SSHOBJECT_H
-
-#include <coreplugin/core_global.h>
-
-#include <QtCore/QMutex>
-#include <QtCore/QSharedPointer>
-#include <QtCore/QWeakPointer>
-
-class ne7ssh;
-
-namespace Core {
-namespace Internal {
-
-class Ne7SshObject
-{
-public:
- typedef QSharedPointer<ne7ssh> Ptr;
-
- static Ne7SshObject *instance();
- static void removeInstance();
-
- Ptr get();
-
-private:
- Ne7SshObject();
- Ne7SshObject(const Ne7SshObject &);
- Ne7SshObject &operator=(const Ne7SshObject &);
-
- static Ne7SshObject *m_instance;
-
- QWeakPointer<ne7ssh> m_weakRef;
- QMutex m_mutex;
-};
-
-} // namespace Internal
-} // namespace Core
-
-#endif // NE7SSHOBJECT_H
diff --git a/src/plugins/coreplugin/ssh/sftpchannel.cpp b/src/plugins/coreplugin/ssh/sftpchannel.cpp
new file mode 100644
index 0000000000..2cb31a3bce
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftpchannel.cpp
@@ -0,0 +1,756 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sftpchannel.h"
+#include "sftpchannel_p.h"
+
+#include "sshdelayedsignal_p.h"
+#include "sshexception_p.h"
+#include "sshsendfacility_p.h"
+
+#include <QtCore/QFile>
+#include <QtCore/QWeakPointer>
+
+namespace Core {
+
+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);
+ }
+} // anonymous namespace
+} // namespace Internal
+
+SftpChannel::SftpChannel(quint32 channelId,
+ Internal::SshSendFacility &sendFacility)
+ : d(new Internal::SftpChannelPrivate(channelId, sendFacility, this))
+{
+}
+
+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::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::createFile(const QString &path, SftpOverwriteMode mode)
+{
+ return d->createJob(Internal::SftpCreateFile::Ptr(
+ new Internal::SftpCreateFile(++d->m_nextJobId, path, 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::SftpUpload::Ptr(
+ new Internal::SftpUpload(++d->m_nextJobId, remoteFilePath, localFile, mode)));
+}
+
+SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath,
+ const QString &localFilePath, SftpOverwriteMode mode)
+{
+ QSharedPointer<QFile> localFile(new QFile(localFilePath));
+ if (mode == SftpSkipExisting && localFile->exists())
+ return SftpInvalidJob;
+ QIODevice::OpenMode openMode = QIODevice::WriteOnly;
+ if (mode == SftpOverwriteExisting)
+ openMode |= QIODevice::Truncate;
+ else if (mode == SftpAppendToExisting)
+ openMode |= QIODevice::Append;
+ if (!localFile->open(openMode))
+ return SftpInvalidJob;
+ return d->createJob(Internal::SftpDownload::Ptr(
+ new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, localFile)));
+}
+
+SftpChannel::~SftpChannel()
+{
+ delete d;
+}
+
+
+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()
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("sftp subsystem initialized");
+#endif
+ sendData(m_outgoingPacket.generateInit(ProtocolVersion).rawData());
+ m_sftpState = InitSent;
+}
+
+void SftpChannelPrivate::handleChannelFailure()
+{
+ if (m_sftpState != SubsystemRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_MSG_CHANNEL_FAILURE packet.");
+ }
+ createDelayedInitFailedSignal(SSH_TR("Server could not start sftp subsystem."));
+ closeChannel();
+}
+
+void SftpChannelPrivate::handleChannelDataInternal(const QByteArray &data)
+{
+ 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)
+{
+ qWarning("Unexpected extended data '%s' of type %d on SFTP channel.",
+ data.data(), type);
+}
+
+void SftpChannelPrivate::handleCurrentPacket()
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Handling SFTP packet of type %d", m_incomingPacket.type());
+#endif
+ 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.",
+ SSH_TR("Unexpected packet of type %d.").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.");
+ }
+
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("sftp init received");
+#endif
+ const quint32 serverVersion = m_incomingPacket.extractServerVersion();
+ if (serverVersion != ProtocolVersion) {
+ createDelayedInitFailedSignal(SSH_TR("Protocol version mismatch: Expected %1, got %2")
+ .arg(serverVersion).arg(ProtocolVersion));
+ closeChannel();
+ } else {
+ m_sftpState = Initialized;
+ createDelayedInitializedSignal();
+ }
+}
+
+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::Upload:
+ handlePutHandle(it);
+ break;
+ default:
+ Q_ASSERT(!"Oh no, I forgot to handle an SFTP operation type!");
+ }
+}
+
+void SftpChannelPrivate::handleLsHandle(const JobMap::Iterator &it)
+{
+ SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
+ sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
+ op->jobId).rawData());
+}
+
+void SftpChannelPrivate::handleCreateFileHandle(const JobMap::Iterator &it)
+{
+ SftpCreateFile::Ptr op = it.value().staticCast<SftpCreateFile>();
+ sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
+ op->jobId).rawData());
+}
+
+void SftpChannelPrivate::handleGetHandle(const 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(const JobMap::Iterator &it)
+{
+ SftpUpload::Ptr op = it.value().staticCast<SftpUpload>();
+
+ // 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();
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("%s: status = %d", Q_FUNC_INFO, response.status);
+#endif
+ 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::Upload:
+ handlePutStatus(it, response);
+ break;
+ case AbstractSftpOperation::MakeDir:
+ case AbstractSftpOperation::RmDir:
+ case AbstractSftpOperation::Rm:
+ case AbstractSftpOperation::Rename:
+ case AbstractSftpOperation::CreateFile:
+ handleStatusGeneric(it, response);
+ break;
+ }
+}
+
+void SftpChannelPrivate::handleStatusGeneric(const JobMap::Iterator &it,
+ const SftpStatusResponse &response)
+{
+ AbstractSftpOperation::Ptr op = it.value();
+ const QString error = errorMessage(response, SSH_TR("Unknown error."));
+ createDelayedJobFinishedSignal(op->jobId, error);
+ m_jobs.erase(it);
+}
+
+void SftpChannelPrivate::handleLsStatus(const JobMap::Iterator &it,
+ const SftpStatusResponse &response)
+{
+ SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
+ switch (op->state) {
+ case SftpListDir::OpenRequested:
+ createDelayedJobFinishedSignal(op->jobId, errorMessage(response.errorString,
+ SSH_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, errorMessage(response.errorString,
+ SSH_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) {
+ const QString error = errorMessage(response,
+ SSH_TR("Failed to close remote directory."));
+ createDelayedJobFinishedSignal(op->jobId, error);
+ }
+ m_jobs.erase(it);
+ break;
+ default:
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_FXP_STATUS packet.");
+ }
+}
+
+void SftpChannelPrivate::handleGetStatus(const JobMap::Iterator &it,
+ const SftpStatusResponse &response)
+{
+ SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
+ switch (op->state) {
+ case SftpDownload::OpenRequested:
+ createDelayedJobFinishedSignal(op->jobId,
+ errorMessage(response.errorString,
+ SSH_TR("Failed to open remote file for reading.")));
+ m_jobs.erase(it);
+ break;
+ case SftpDownload::Open:
+ if (op->statRequested) {
+ reportRequestError(op, errorMessage(response.errorString,
+ SSH_TR("Failed to stat remote file.")));
+ sendTransferCloseHandle(op, response.requestId);
+ } else {
+ if ((response.status != SSH_FX_EOF || response.requestId != op->eofId)
+ && !op->hasError)
+ reportRequestError(op, errorMessage(response.errorString,
+ SSH_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)
+ createDelayedJobFinishedSignal(op->jobId);
+ else
+ reportRequestError(op, errorMessage(response.errorString,
+ SSH_TR("Failed to close remote file.")));
+ }
+ removeTransferRequest(it);
+ break;
+ default:
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_FXP_STATUS packet.");
+ }
+}
+
+void SftpChannelPrivate::handlePutStatus(const JobMap::Iterator &it,
+ const SftpStatusResponse &response)
+{
+ SftpUpload::Ptr job = it.value().staticCast<SftpUpload>();
+ switch (job->state) {
+ case SftpUpload::OpenRequested:
+ createDelayedJobFinishedSignal(job->jobId,
+ errorMessage(response.errorString,
+ SSH_TR("Failed to open remote file for writing.")));
+ m_jobs.erase(it);
+ break;
+ case SftpUpload::Open:
+ if (response.status == SSH_FX_OK) {
+ sendWriteRequest(it);
+ } else if(!job->hasError) {
+ reportRequestError(job, errorMessage(response.errorString,
+ SSH_TR("Failed to write remote file.")));
+ finishTransferRequest(it);
+ }
+ break;
+ case SftpUpload::CloseRequested:
+ Q_ASSERT(job->inFlightCount == 1);
+ if (!job->hasError) {
+ const QString error = errorMessage(response,
+ SSH_TR("Failed to close remote file."));
+ createDelayedJobFinishedSignal(job->jobId, 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.");
+ }
+
+ for (int i = 0; i < response.files.count(); ++i) {
+ const SftpFile &file = response.files.at(i);
+ createDelayedDataAvailableSignal(op->jobId, file.fileName);
+ }
+ 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->seek(op->offsets[response.requestId])) {
+ reportRequestError(op, op->localFile->errorString());
+ finishTransferRequest(it);
+ return;
+ }
+
+ if (op->localFile->write(response.data) != response.data.size()) {
+ reportRequestError(op, op->localFile->errorString());
+ finishTransferRequest(it);
+ return;
+ }
+
+ 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);
+ 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::Upload
+ || 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;
+ spawnReadRequests(op);
+ } else {
+ SftpUpload::Ptr op = transfer.staticCast<SftpUpload>();
+ if (response.attrs.sizePresent) {
+ op->offset = response.attrs.size;
+ spawnWriteRequests(it);
+ } else {
+ reportRequestError(op, SSH_TR("Cannot append to remote file: "
+ "Server does not support file size attribute."));
+ sendTransferCloseHandle(op, op->jobId);
+ }
+ }
+}
+
+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()
+{
+ createClosedSignal();
+}
+
+void SftpChannelPrivate::handleOpenSuccessInternal()
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("SFTP session started");
+#endif
+ m_sendFacility.sendSftpPacket(remoteChannel());
+ m_sftpState = SubsystemRequested;
+}
+
+void SftpChannelPrivate::handleOpenFailureInternal()
+{
+ if (channelState() != SessionRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
+ }
+ createDelayedInitFailedSignal(SSH_TR("Server could not start session."));
+}
+
+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 QString &error)
+{
+ createDelayedJobFinishedSignal(job->jobId, error);
+ job->hasError = true;
+}
+
+void SftpChannelPrivate::finishTransferRequest(const 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::removeTransferRequest(const JobMap::Iterator &it)
+{
+ --it.value().staticCast<AbstractSftpTransfer>()->inFlightCount;
+ m_jobs.erase(it);
+}
+
+void SftpChannelPrivate::sendWriteRequest(const JobMap::Iterator &it)
+{
+ SftpUpload::Ptr job = it.value().staticCast<SftpUpload>();
+ QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize);
+ if (job->localFile->error() != QFile::NoError) {
+ if (!job->hasError) {
+ reportRequestError(job, SSH_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(const JobMap::Iterator &it)
+{
+ SftpUpload::Ptr op = it.value().staticCast<SftpUpload>();
+ op->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
+ sendWriteRequest(it);
+ for (int i = 1; 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);
+ }
+}
+
+void SftpChannelPrivate::createDelayedInitFailedSignal(const QString &reason)
+{
+ new SftpInitializationFailedSignal(this, QWeakPointer<SftpChannel>(m_sftp),
+ reason);
+}
+
+void SftpChannelPrivate::emitInitializationFailedSignal(const QString &reason)
+{
+ emit m_sftp->initializationFailed(reason);
+}
+
+void SftpChannelPrivate::createDelayedInitializedSignal()
+{
+ new SftpInitializedSignal(this, QWeakPointer<SftpChannel>(m_sftp));
+}
+
+void SftpChannelPrivate::emitInitialized()
+{
+ emit m_sftp->initialized();
+}
+
+void SftpChannelPrivate::createDelayedJobFinishedSignal(SftpJobId jobId,
+ const QString &error)
+{
+ new SftpJobFinishedSignal(this, QWeakPointer<SftpChannel>(m_sftp), jobId, error);
+}
+
+void SftpChannelPrivate::emitJobFinished(SftpJobId jobId, const QString &error)
+{
+ emit m_sftp->finished(jobId, error);
+}
+
+void SftpChannelPrivate::createDelayedDataAvailableSignal(SftpJobId jobId,
+ const QString &data)
+{
+ new SftpDataAvailableSignal(this, QWeakPointer<SftpChannel>(m_sftp), jobId, data);
+}
+
+void SftpChannelPrivate::emitDataAvailable(SftpJobId jobId, const QString &data)
+{
+ emit m_sftp->dataAvailable(jobId, data);
+}
+
+void SftpChannelPrivate::createClosedSignal()
+{
+ new SftpClosedSignal(this, QWeakPointer<SftpChannel>(m_sftp));
+}
+
+void SftpChannelPrivate::emitClosed()
+{
+ emit m_sftp->closed();
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sftpchannel.h b/src/plugins/coreplugin/ssh/sftpchannel.h
new file mode 100644
index 0000000000..6b4ffea7e5
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftpchannel.h
@@ -0,0 +1,120 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SFTCHANNEL_H
+#define SFTCHANNEL_H
+
+#include "sftpdefs.h"
+#include "sftpincomingpacket_p.h"
+
+#include <coreplugin/core_global.h>
+
+#include <QtCore/QByteArray>
+#include <QtCore/QObject>
+#include <QtCore/QSharedPointer>
+#include <QtCore/QString>
+
+namespace Core {
+
+namespace Internal {
+class SftpChannelPrivate;
+class SshChannelManager;
+class SshSendFacility;
+} // namespace Internal
+
+/*
+ * 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 finishedWithError() signal is emitted for the respective job.
+ * Note that directory names must not have a trailing slash.
+ */
+class CORE_EXPORT SftpChannel : public QObject
+{
+ Q_OBJECT
+
+ friend class Internal::SftpChannelPrivate;
+ friend class Internal::SshChannelManager;
+public:
+ typedef QSharedPointer<SftpChannel> Ptr;
+
+ enum State { Uninitialized, Initializing, Initialized, Closing, Closed };
+ State state() const;
+
+ void initialize();
+ void closeChannel();
+
+ SftpJobId listDirectory(const QString &dirPath);
+ SftpJobId createDirectory(const QString &dirPath);
+ SftpJobId removeDirectory(const QString &dirPath);
+ SftpJobId removeFile(const QString &filePath);
+ SftpJobId renameFileOrDirectory(const QString &oldPath,
+ const QString &newPath);
+ SftpJobId createFile(const QString &filePath, SftpOverwriteMode mode);
+ SftpJobId uploadFile(const QString &localFilePath,
+ const QString &remoteFilePath, SftpOverwriteMode mode);
+ SftpJobId downloadFile(const QString &remoteFilePath,
+ const QString &localFilePath, SftpOverwriteMode mode);
+
+ ~SftpChannel();
+
+signals:
+ void initialized();
+ void initializationFailed(const QString &reason);
+ void closed();
+
+ // error.isEmpty <=> finished successfully
+ void finished(Core::SftpJobId job, const QString &error = QString());
+
+ /*
+ * This signal is only emitted by the "List Directory" operation,
+ * one file at a time.
+ */
+ void dataAvailable(SftpJobId job, const QString &data);
+
+private:
+ SftpChannel(quint32 channelId, Internal::SshSendFacility &sendFacility);
+
+ Internal::SftpChannelPrivate *d;
+};
+
+} // namespace Core
+
+#endif // SFTPCHANNEL_H
diff --git a/src/plugins/coreplugin/ssh/sftpchannel_p.h b/src/plugins/coreplugin/ssh/sftpchannel_p.h
new file mode 100644
index 0000000000..8d27a2c01d
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftpchannel_p.h
@@ -0,0 +1,130 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#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 <QtCore/QByteArray>
+#include <QtCore/QMap>
+
+namespace Core {
+class SftpChannel;
+namespace Internal {
+
+class SftpChannelPrivate : public AbstractSshChannel
+{
+ friend class Core::SftpChannel;
+public:
+
+ enum SftpState { Inactive, SubsystemRequested, InitSent, Initialized };
+
+ virtual void handleChannelSuccess();
+ virtual void handleChannelFailure();
+
+ virtual void closeHook();
+
+ void emitInitializationFailedSignal(const QString &reason);
+ void emitInitialized();
+ void emitJobFinished(SftpJobId jobId, const QString &error);
+ void emitDataAvailable(SftpJobId jobId, const QString &data);
+ void emitClosed();
+
+private:
+ typedef QMap<SftpJobId, AbstractSftpOperation::Ptr> JobMap;
+
+ SftpChannelPrivate(quint32 channelId, SshSendFacility &sendFacility,
+ SftpChannel *sftp);
+ SftpJobId createJob(const AbstractSftpOperation::Ptr &job);
+
+ virtual void handleOpenSuccessInternal();
+ virtual void handleOpenFailureInternal();
+ virtual void handleChannelDataInternal(const QByteArray &data);
+ virtual void handleChannelExtendedDataInternal(quint32 type,
+ const QByteArray &data);
+
+ void handleCurrentPacket();
+ void handleServerVersion();
+ void handleHandle();
+ void handleStatus();
+ void handleName();
+ void handleReadData();
+ void handleAttrs();
+
+ void handleStatusGeneric(const JobMap::Iterator &it,
+ const SftpStatusResponse &response);
+ void handleLsStatus(const JobMap::Iterator &it,
+ const SftpStatusResponse &response);
+ void handleGetStatus(const JobMap::Iterator &it,
+ const SftpStatusResponse &response);
+ void handlePutStatus(const JobMap::Iterator &it,
+ const SftpStatusResponse &response);
+
+ void handleLsHandle(const JobMap::Iterator &it);
+ void handleCreateFileHandle(const JobMap::Iterator &it);
+ void handleGetHandle(const JobMap::Iterator &it);
+ void handlePutHandle(const JobMap::Iterator &it);
+
+ void spawnReadRequests(const SftpDownload::Ptr &job);
+ void spawnWriteRequests(const JobMap::Iterator &it);
+ void sendReadRequest(const SftpDownload::Ptr &job, quint32 requestId);
+ void sendWriteRequest(const JobMap::Iterator &it);
+ void finishTransferRequest(const JobMap::Iterator &it);
+ void removeTransferRequest(const JobMap::Iterator &it);
+ void reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job,
+ const QString &error);
+ void sendTransferCloseHandle(const AbstractSftpTransfer::Ptr &job,
+ quint32 requestId);
+
+ void createDelayedInitFailedSignal(const QString &reason);
+ void createDelayedInitializedSignal();
+ void createDelayedJobFinishedSignal(SftpJobId jobId,
+ const QString &error = QString());
+ void createDelayedDataAvailableSignal(SftpJobId jobId, const QString &data);
+ void createClosedSignal();
+
+ 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 Core
+
+#endif // SFTPCHANNEL_P_H
diff --git a/src/plugins/coreplugin/ssh/sftpdefs.cpp b/src/plugins/coreplugin/ssh/sftpdefs.cpp
new file mode 100644
index 0000000000..6a2f6de45e
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftpdefs.cpp
@@ -0,0 +1,32 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sftpdefs.h"
+
+namespace Core { const SftpJobId SftpInvalidJob = 0; }
diff --git a/src/plugins/coreplugin/ssh/sftpdefs.h b/src/plugins/coreplugin/ssh/sftpdefs.h
new file mode 100644
index 0000000000..5f59582a05
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftpdefs.h
@@ -0,0 +1,48 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SFTPDEFS_H
+#define SFTPDEFS_H
+
+#include <coreplugin/core_global.h>
+
+#include <QtCore/QtGlobal>
+
+namespace Core {
+
+typedef quint32 SftpJobId;
+CORE_EXPORT extern const SftpJobId SftpInvalidJob;
+
+enum SftpOverwriteMode {
+ SftpOverwriteExisting, SftpAppendToExisting, SftpSkipExisting
+};
+
+} // namespace Core
+
+#endif // SFTPDEFS_H
diff --git a/src/plugins/coreplugin/ssh/sftpincomingpacket.cpp b/src/plugins/coreplugin/ssh/sftpincomingpacket.cpp
new file mode 100644
index 0000000000..804bdb21d5
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftpincomingpacket.cpp
@@ -0,0 +1,230 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sftpincomingpacket_p.h"
+
+#include "sshexception_p.h"
+#include "sshpacketparser_p.h"
+
+namespace Core {
+namespace Internal {
+
+namespace {
+ const int SSH_FILEXFER_ATTR_SIZE = 0x00000001;
+ const int SSH_FILEXFER_ATTR_UIDGID = 0x00000002;
+ const int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004;
+ const int SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008;
+ const int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000;
+} // anonymous namespace
+
+SftpIncomingPacket::SftpIncomingPacket() : m_length(0)
+{
+}
+
+void SftpIncomingPacket::consumeData(QByteArray &newData)
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("%s: current data size = %d, new data size = %d", Q_FUNC_INFO,
+ m_data.size(), newData.size());
+#endif
+
+ if (isComplete() || dataSize() + newData.size() < 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 (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 (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 (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 (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 (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 (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 Core
diff --git a/src/plugins/coreplugin/ssh/sftpincomingpacket_p.h b/src/plugins/coreplugin/ssh/sftpincomingpacket_p.h
new file mode 100644
index 0000000000..5a5b8d42fe
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftpincomingpacket_p.h
@@ -0,0 +1,111 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SFTPINCOMINGPACKET_P_H
+#define SFTPINCOMINGPACKET_P_H
+
+#include "sftppacket_p.h"
+
+namespace Core {
+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 Core
+
+#endif // SFTPINCOMINGPACKET_P_H
diff --git a/src/plugins/coreplugin/ssh/sftpoperation.cpp b/src/plugins/coreplugin/ssh/sftpoperation.cpp
new file mode 100644
index 0000000000..8acb126db1
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftpoperation.cpp
@@ -0,0 +1,176 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sftpoperation_p.h"
+
+#include "sftpoutgoingpacket_p.h"
+
+#include <QtCore/QTime>
+#include <QtCore/QFile>
+
+namespace Core {
+namespace Internal {
+
+AbstractSftpOperation::AbstractSftpOperation(SftpJobId jobId) : jobId(jobId)
+{
+}
+
+AbstractSftpOperation::~AbstractSftpOperation() { }
+
+
+SftpMakeDir::SftpMakeDir(SftpJobId jobId, const QString &path)
+ : AbstractSftpOperation(jobId), remoteDir(path)
+{
+}
+
+SftpOutgoingPacket &SftpMakeDir::initialPacket(SftpOutgoingPacket &packet)
+{
+ return packet.generateMkDir(remoteDir, jobId);
+}
+
+
+SftpRmDir::SftpRmDir(SftpJobId, const QString &path)
+ : AbstractSftpOperation(jobId), 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);
+}
+
+
+AbstractSftpOperationWithHandle::AbstractSftpOperationWithHandle(SftpJobId jobId,
+ const QString &remotePath)
+ : AbstractSftpOperation(jobId),
+ remotePath(remotePath), state(Inactive), hasError(false)
+{
+}
+
+AbstractSftpOperationWithHandle::~AbstractSftpOperationWithHandle() { }
+
+
+SftpListDir::SftpListDir(SftpJobId jobId, const QString &path)
+ : AbstractSftpOperationWithHandle(jobId, path)
+{
+}
+
+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, jobId);
+}
+
+
+const int AbstractSftpTransfer::MaxInFlightCount = 10; // Experimentally found to be enough.
+
+AbstractSftpTransfer::AbstractSftpTransfer(SftpJobId jobId, const QString &remotePath,
+ const QSharedPointer<QFile> &localFile)
+ : AbstractSftpOperationWithHandle(jobId, remotePath),
+ localFile(localFile), fileSize(0), offset(0), inFlightCount(0),
+ statRequested(false)
+{
+}
+
+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<QFile> &localFile)
+ : AbstractSftpTransfer(jobId, remotePath, localFile), eofId(SftpInvalidJob)
+{
+}
+
+SftpOutgoingPacket &SftpDownload::initialPacket(SftpOutgoingPacket &packet)
+{
+ state = OpenRequested;
+ return packet.generateOpenFileForReading(remotePath, jobId);
+}
+
+
+SftpUpload::SftpUpload(SftpJobId jobId, const QString &remotePath,
+ const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode)
+ : AbstractSftpTransfer(jobId, remotePath, localFile), mode(mode)
+{
+ fileSize = localFile->size();
+}
+
+SftpOutgoingPacket &SftpUpload::initialPacket(SftpOutgoingPacket &packet)
+{
+ state = OpenRequested;
+ return packet.generateOpenFileForWriting(remotePath, mode, jobId);
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sftpoperation_p.h b/src/plugins/coreplugin/ssh/sftpoperation_p.h
new file mode 100644
index 0000000000..6598781332
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftpoperation_p.h
@@ -0,0 +1,193 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SFTPOPERATION_P_H
+#define SFTPOPERATION_P_H
+
+#include "sftpdefs.h"
+
+#include <QtCore/QByteArray>
+#include <QtCore/QMap>
+#include <QtCore/QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QFile;
+QT_END_NAMESPACE
+
+namespace Core {
+namespace Internal {
+
+class SftpOutgoingPacket;
+
+struct AbstractSftpOperation
+{
+ typedef QSharedPointer<AbstractSftpOperation> Ptr;
+ enum Type {
+ ListDir, MakeDir, RmDir, Rm, Rename, CreateFile, Download, Upload
+ };
+
+ 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 SftpMakeDir : public AbstractSftpOperation
+{
+ typedef QSharedPointer<SftpMakeDir> Ptr;
+
+ SftpMakeDir(SftpJobId jobId, const QString &path);
+ virtual Type type() const { return MakeDir; }
+ virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+ const QString remoteDir;
+};
+
+struct SftpRmDir : public AbstractSftpOperation
+{
+ typedef QSharedPointer<SftpRmDir> Ptr;
+
+ SftpRmDir(SftpJobId jobId, 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 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);
+ virtual Type type() const { return ListDir; }
+ virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+};
+
+
+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<QFile> &localFile);
+ void calculateInFlightCount(quint32 chunkSize);
+
+ static const int MaxInFlightCount;
+
+ const QSharedPointer<QFile> 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<QFile> &localFile);
+ virtual Type type() const { return Download; }
+ virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+ QMap<quint32, quint32> offsets;
+ SftpJobId eofId;
+};
+
+struct SftpUpload : public AbstractSftpTransfer
+{
+ typedef QSharedPointer<SftpUpload> Ptr;
+
+ SftpUpload(SftpJobId jobId, const QString &remotePath,
+ const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode);
+ virtual Type type() const { return Upload; }
+ virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+ SftpOverwriteMode mode;
+};
+
+} // namespace Internal
+} // namespace Core
+
+#endif // SFTPOPERATION_P_H
diff --git a/src/plugins/coreplugin/ssh/sftpoutgoingpacket.cpp b/src/plugins/coreplugin/ssh/sftpoutgoingpacket.cpp
new file mode 100644
index 0000000000..57fd85467e
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftpoutgoingpacket.cpp
@@ -0,0 +1,202 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sftpoutgoingpacket_p.h"
+
+#include "sshpacket_p.h"
+
+#include <QtCore/QtEndian>
+
+namespace Core {
+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::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 requestId)
+{
+ return generateOpenFile(path, Write, mode, requestId);
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFileForReading(const QString &path,
+ quint32 requestId)
+{
+ // Note: Overwrite mode is irrelevant and will be ignored.
+ return generateOpenFile(path, Read, SftpSkipExisting, 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::generateOpenFile(const QString &path,
+ OpenType openType, SftpOverwriteMode mode, quint32 requestId)
+{
+ quint32 pFlags;
+ 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;
+ }
+ return init(SSH_FXP_OPEN, requestId).appendString(path).appendInt(pFlags)
+ .appendInt(DefaultAttributes).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::init(SftpPacketType type,
+ quint32 requestId)
+{
+ m_data.resize(TypeOffset + 1);
+ m_data[TypeOffset] = type;
+ if (type != SSH_FXP_INIT) {
+ appendInt(requestId);
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Generating SFTP packet of type %d with request id %u", type,
+ requestId);
+#endif
+ }
+ 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;
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sftpoutgoingpacket_p.h b/src/plugins/coreplugin/ssh/sftpoutgoingpacket_p.h
new file mode 100644
index 0000000000..4f456e8754
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftpoutgoingpacket_p.h
@@ -0,0 +1,83 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SFTPOUTGOINGPACKET_P_H
+#define SFTPOUTGOINGPACKET_P_H
+
+#include "sftppacket_p.h"
+#include "sftpdefs.h"
+
+namespace Core {
+namespace Internal {
+
+class SftpOutgoingPacket : public AbstractSftpPacket
+{
+public:
+ SftpOutgoingPacket();
+ SftpOutgoingPacket &generateInit(quint32 version);
+ 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 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);
+
+private:
+ static QByteArray encodeString(const QString &string);
+
+ enum OpenType { Read, Write };
+ SftpOutgoingPacket &generateOpenFile(const QString &path, OpenType openType,
+ SftpOverwriteMode mode, 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 Core
+
+#endif // SFTPOUTGOINGPACKET_P_H
diff --git a/src/plugins/coreplugin/ssh/sftppacket.cpp b/src/plugins/coreplugin/ssh/sftppacket.cpp
new file mode 100644
index 0000000000..0064bf39fa
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftppacket.cpp
@@ -0,0 +1,54 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sftppacket_p.h"
+
+#include "sshpacketparser_p.h"
+
+namespace Core {
+namespace Internal {
+
+const quint32 AbstractSftpPacket::MaxDataSize = 32768;
+const quint32 AbstractSftpPacket::MaxPacketSize = 34000;
+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 Core
diff --git a/src/plugins/coreplugin/ssh/sftppacket_p.h b/src/plugins/coreplugin/ssh/sftppacket_p.h
new file mode 100644
index 0000000000..aae1a15a97
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sftppacket_p.h
@@ -0,0 +1,108 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SFTPPACKET_P_H
+#define SFTPPACKET_P_H
+
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+#include <QtCore/QString>
+
+namespace Core {
+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
+};
+
+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 Core
+
+#endif // SFTPPACKET_P_H
diff --git a/src/plugins/coreplugin/ssh/sshbotanconversions_p.h b/src/plugins/coreplugin/ssh/sshbotanconversions_p.h
new file mode 100644
index 0000000000..0582977942
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshbotanconversions_p.h
@@ -0,0 +1,97 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef BYTEARRAYCONVERSIONS_P_H
+#define BYTEARRAYCONVERSIONS_P_H
+
+#include "sshcapabilities_p.h"
+
+#include <botan/rng.h>
+#include <botan/secmem.h>
+
+namespace Core {
+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::SecureVector<Botan::byte> &v)
+{
+ return QByteArray(reinterpret_cast<const char *>(v.begin()), v.size());
+}
+
+inline const char *botanKeyExchangeAlgoName(const QByteArray &rfcAlgoName)
+{
+ Q_ASSERT(rfcAlgoName == SshCapabilities::DiffieHellmanGroup1Sha1
+ || rfcAlgoName == SshCapabilities::DiffieHellmanGroup14Sha1);
+ return rfcAlgoName == SshCapabilities::DiffieHellmanGroup1Sha1
+ ? "modp/ietf/1024" : "modp/ietf/2048";
+}
+
+inline const char *botanCryptAlgoName(const QByteArray &rfcAlgoName)
+{
+ Q_ASSERT(rfcAlgoName == SshCapabilities::CryptAlgo3Des
+ || rfcAlgoName == SshCapabilities::CryptAlgoAes128);
+ return rfcAlgoName == SshCapabilities::CryptAlgo3Des
+ ? "TripleDES" : "AES-128";
+}
+
+inline const char *botanEmsaAlgoName(const QByteArray &rfcAlgoName)
+{
+ Q_ASSERT(rfcAlgoName == SshCapabilities::PubKeyDss
+ || rfcAlgoName == SshCapabilities::PubKeyRsa);
+ return rfcAlgoName == SshCapabilities::PubKeyDss
+ ? "EMSA1(SHA-1)" : "EMSA3(SHA-1)";
+}
+
+inline const char *botanSha1Name() { return "SHA-1"; }
+
+inline const char *botanHMacAlgoName(const QByteArray &rfcAlgoName)
+{
+ Q_ASSERT(rfcAlgoName == SshCapabilities::HMacSha1);
+ return botanSha1Name();
+}
+
+inline quint32 botanHMacKeyLen(const QByteArray &rfcAlgoName)
+{
+ Q_ASSERT(rfcAlgoName == SshCapabilities::HMacSha1);
+ return 20;
+}
+
+} // namespace Internal
+} // namespace Core
+
+#endif // BYTEARRAYCONVERSIONS_P_H
diff --git a/src/plugins/coreplugin/ssh/sshcapabilities.cpp b/src/plugins/coreplugin/ssh/sshcapabilities.cpp
new file mode 100644
index 0000000000..56db394206
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshcapabilities.cpp
@@ -0,0 +1,103 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshcapabilities_p.h"
+
+#include "sshexception_p.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QString>
+
+namespace Core {
+namespace Internal {
+
+namespace {
+ QByteArray listAsByteArray(const QList<QByteArray> &list)
+ {
+ QByteArray array;
+ foreach(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 QList<QByteArray> SshCapabilities::KeyExchangeMethods
+ = QList<QByteArray>() << SshCapabilities::DiffieHellmanGroup1Sha1
+ << SshCapabilities::DiffieHellmanGroup14Sha1;
+
+const QByteArray SshCapabilities::PubKeyDss("ssh-dss");
+const QByteArray SshCapabilities::PubKeyRsa("ssh-rsa");
+const QList<QByteArray> SshCapabilities::PublicKeyAlgorithms
+ = QList<QByteArray>() << SshCapabilities::PubKeyRsa
+ << SshCapabilities::PubKeyDss;
+
+const QByteArray SshCapabilities::CryptAlgo3Des("3des-cbc");
+const QByteArray SshCapabilities::CryptAlgoAes128("aes128-cbc");
+const QList<QByteArray> SshCapabilities::EncryptionAlgorithms
+ = QList<QByteArray>() << SshCapabilities::CryptAlgoAes128
+ << SshCapabilities::CryptAlgo3Des;
+
+const QByteArray SshCapabilities::HMacSha1("hmac-sha1");
+const QByteArray SshCapabilities::HMacSha196("hmac-sha1-96");
+const QList<QByteArray> SshCapabilities::MacAlgorithms
+ = QList<QByteArray>() /* << SshCapabilities::HMacSha196 */
+ << SshCapabilities::HMacSha1;
+
+const QList<QByteArray> SshCapabilities::CompressionAlgorithms
+ = QList<QByteArray>() << "none";
+
+const QByteArray SshCapabilities::SshConnectionService("ssh-connection");
+
+const QByteArray SshCapabilities::PublicKeyAuthMethod("publickey");
+const QByteArray SshCapabilities::PasswordAuthMethod("password");
+
+
+QByteArray SshCapabilities::findBestMatch(const QList<QByteArray> &myCapabilities,
+ const QList<QByteArray> &serverCapabilities)
+{
+ foreach (const QByteArray &myCapability, myCapabilities) {
+ if (serverCapabilities.contains(myCapability))
+ return myCapability;
+ }
+
+ throw SshServerException(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ "Server and client capabilities don't match.",
+ QCoreApplication::translate("SshConnection",
+ "Server and client capabilities don't match. "
+ "Client list was: %1.\nServer list was %2.")
+ .arg(listAsByteArray(myCapabilities).data())
+ .arg(listAsByteArray(serverCapabilities).data()));
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sshcapabilities_p.h b/src/plugins/coreplugin/ssh/sshcapabilities_p.h
new file mode 100644
index 0000000000..7c58c830f5
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshcapabilities_p.h
@@ -0,0 +1,72 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef CAPABILITIES_P_H
+#define CAPABILITIES_P_H
+
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+
+namespace Core {
+namespace Internal {
+
+class SshCapabilities
+{
+public:
+ static const QByteArray DiffieHellmanGroup1Sha1;
+ static const QByteArray DiffieHellmanGroup14Sha1;
+ static const QList<QByteArray> KeyExchangeMethods;
+
+ static const QByteArray PubKeyDss;
+ static const QByteArray PubKeyRsa;
+ static const QList<QByteArray> PublicKeyAlgorithms;
+
+ static const QByteArray CryptAlgo3Des;
+ static const QByteArray CryptAlgoAes128;
+ static const QList<QByteArray> EncryptionAlgorithms;
+
+ static const QByteArray HMacSha1;
+ static const QByteArray HMacSha196;
+ static const QList<QByteArray> MacAlgorithms;
+
+ static const QList<QByteArray> CompressionAlgorithms;
+
+ static const QByteArray SshConnectionService;
+
+ static const QByteArray PublicKeyAuthMethod;
+ static const QByteArray PasswordAuthMethod;
+
+ static QByteArray findBestMatch(const QList<QByteArray> &myCapabilities,
+ const QList<QByteArray> &serverCapabilities);
+};
+
+} // namespace Internal
+} // namespace Core
+
+#endif // CAPABILITIES_P_H
diff --git a/src/plugins/coreplugin/ssh/sshchannel.cpp b/src/plugins/coreplugin/ssh/sshchannel.cpp
new file mode 100644
index 0000000000..6e1b9c43f5
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshchannel.cpp
@@ -0,0 +1,244 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshchannel_p.h"
+
+#include "sshincomingpacket_p.h"
+#include "sshsendfacility_p.h"
+
+#include <botan/exceptn.h>
+
+namespace Core {
+namespace Internal {
+
+namespace {
+ const quint32 MinMaxPacketSize = 32768;
+ const quint32 MaxPacketSize = 16 * 1024 * 1024;
+ const quint32 InitialWindowSize = MaxPacketSize;
+ const quint32 NoChannel = 0xffffffffu;
+} // anonymous namespace
+
+AbstractSshChannel::AbstractSshChannel(quint32 channelId,
+ SshSendFacility &sendFacility)
+ : m_sendFacility(sendFacility), m_localChannel(channelId),
+ m_remoteChannel(NoChannel), m_localWindowSize(InitialWindowSize),
+ m_remoteWindowSize(0), m_state(Inactive)
+{
+}
+
+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);
+ } catch (Botan::Exception &e) {
+ m_errorString = QString::fromAscii(e.what());
+ closeChannel();
+ }
+}
+
+void AbstractSshChannel::sendData(const QByteArray &data)
+{
+ try {
+ m_sendBuffer += data;
+ flushSendBuffer();
+ } catch (Botan::Exception &e) {
+ m_errorString = QString::fromAscii(e.what());
+ closeChannel();
+ }
+}
+
+void AbstractSshChannel::handleWindowAdjust(quint32 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()
+{
+ const quint32 bytesToSend
+ = qMin<quint32>(m_remoteWindowSize, m_sendBuffer.size());
+ if (bytesToSend > 0) {
+ 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)
+{
+ if (m_state != SessionRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Invalid SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet.");
+ }
+
+ if (remoteMaxPacketSize < MinMaxPacketSize) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Maximum packet size too low.");
+ }
+
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Channel opened. remote channel id: %u, remote window size: %u, "
+ "remote max packet size: %u",
+ remoteChannelId, remoteWindowSize, remoteMaxPacketSize);
+#endif
+ m_remoteChannel = remoteChannelId;
+ m_remoteWindowSize = remoteWindowSize;
+ m_remoteMaxPacketSize = remoteMaxPacketSize;
+ setChannelState(SessionEstablished);
+ handleOpenSuccessInternal();
+}
+
+void AbstractSshChannel::handleOpenFailure(const QString &reason)
+{
+ if (m_state != SessionRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Invalid SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
+ }
+
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Channel open request failed for channel %u", m_localChannel);
+#endif
+ m_errorString = reason;
+ handleOpenFailureInternal();
+}
+
+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;
+}
+
+void AbstractSshChannel::handleChannelClose()
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Receiving CLOSE for channel %u", m_localChannel);
+#endif
+ 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)
+{
+ qWarning("Ignoring unknown request type '%s'",
+ packet.extractChannelRequestType().data());
+}
+
+int AbstractSshChannel::handleChannelOrExtendedChannelData(const QByteArray &data)
+{
+ checkChannelActive();
+
+ const int bytesToDeliver = qMin<quint32>(data.size(), maxDataSize());
+ if (bytesToDeliver != data.size())
+ qWarning("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_state != Closed) {
+ if (m_state == Inactive) {
+ setChannelState(Closed);
+ } else {
+ setChannelState(CloseRequested);
+ m_sendFacility.sendChannelEofPacket(m_remoteChannel);
+ m_sendFacility.sendChannelClosePacket(m_remoteChannel);
+ }
+ }
+}
+
+void AbstractSshChannel::checkChannelActive()
+{
+ 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 Core
diff --git a/src/plugins/coreplugin/ssh/sshchannel_p.h b/src/plugins/coreplugin/ssh/sshchannel_p.h
new file mode 100644
index 0000000000..993357d871
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshchannel_p.h
@@ -0,0 +1,111 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHCHANNEL_P_H
+#define SSHCHANNEL_P_H
+
+#include <QtCore/QByteArray>
+#include <QtCore/QString>
+
+namespace Core {
+namespace Internal {
+
+class SshIncomingPacket;
+class SshSendFacility;
+
+class AbstractSshChannel
+{
+public:
+ enum ChannelState {
+ Inactive, SessionRequested, SessionEstablished, CloseRequested, Closed
+ };
+
+ ChannelState channelState() const { return m_state; }
+ void setChannelState(ChannelState state);
+
+ void setError(const QString &error) { m_errorString = error; }
+ QString errorString() const { return m_errorString; }
+
+ quint32 localChannelId() const { return m_localChannel; }
+ quint32 remoteChannel() const { return m_remoteChannel; }
+
+ virtual void handleChannelSuccess()=0;
+ virtual void handleChannelFailure()=0;
+ virtual void handleChannelRequest(const SshIncomingPacket &packet);
+
+ virtual void closeHook()=0;
+
+ void handleOpenSuccess(quint32 remoteChannelId, quint32 remoteWindowSize,
+ quint32 remoteMaxPacketSize);
+ void handleOpenFailure(const QString &reason);
+ void handleWindowAdjust(quint32 bytesToAdd);
+ void handleChannelEof();
+ void handleChannelClose();
+ void handleChannelData(const QByteArray &data);
+ void handleChannelExtendedData(quint32 type, const QByteArray &data);
+
+ void requestSessionStart();
+ void sendData(const QByteArray &data);
+ void closeChannel();
+
+ virtual ~AbstractSshChannel();
+
+protected:
+ AbstractSshChannel(quint32 channelId, SshSendFacility &sendFacility);
+
+ quint32 maxDataSize() const;
+ void checkChannelActive();
+
+ SshSendFacility &m_sendFacility;
+
+private:
+ virtual void handleOpenSuccessInternal()=0;
+ virtual void handleOpenFailureInternal()=0;
+ virtual void handleChannelDataInternal(const QByteArray &data)=0;
+ virtual void handleChannelExtendedDataInternal(quint32 type,
+ const QByteArray &data)=0;
+
+ void setState(ChannelState newState);
+ 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;
+ QString m_errorString;
+};
+
+} // namespace Internal
+} // namespace Core
+
+#endif // SSHCHANNEL_P_H
diff --git a/src/plugins/coreplugin/ssh/sshchannelmanager.cpp b/src/plugins/coreplugin/ssh/sshchannelmanager.cpp
new file mode 100644
index 0000000000..c7d3113697
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshchannelmanager.cpp
@@ -0,0 +1,188 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshchannelmanager_p.h"
+
+#include "sftpchannel.h"
+#include "sftpchannel_p.h"
+#include "sshincomingpacket_p.h"
+#include "sshremoteprocess.h"
+#include "sshremoteprocess_p.h"
+#include "sshsendfacility_p.h"
+
+#include <QtCore/QList>
+
+namespace Core {
+namespace Internal {
+
+SshChannelManager::SshChannelManager(SshSendFacility &sendFacility)
+ : m_sendFacility(sendFacility), m_nextLocalChannelId(0)
+{
+}
+
+SshChannelManager::~SshChannelManager() {}
+
+void SshChannelManager::handleChannelRequest(const SshIncomingPacket &packet)
+{
+ lookupChannel(packet.extractRecipientChannel())
+ ->handleChannelRequest(packet);
+}
+
+void SshChannelManager::handleChannelOpen(const SshIncomingPacket &)
+{
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Server tried to open channel on client.");
+}
+
+void SshChannelManager::handleChannelOpenFailure(const SshIncomingPacket &packet)
+{
+ const SshChannelOpenFailure &failure = packet.extractChannelOpenFailure();
+ ChannelIterator it = lookupChannelAsIterator(failure.localChannel);
+ try {
+ it.value()->handleOpenFailure(failure.reasonString);
+ } catch (SshServerException &e) {
+ removeChannel(it);
+ throw e;
+ }
+ 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);
+ }
+}
+
+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.",
+ SSH_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() ? 0 : it.value();
+}
+
+Core::SshRemoteProcess::Ptr SshChannelManager::createRemoteProcess(const QByteArray &command)
+{
+ SshRemoteProcess::Ptr proc(new SshRemoteProcess(command, m_nextLocalChannelId++, m_sendFacility));
+ insertChannel(proc->d, proc);
+ return proc;
+}
+
+Core::SftpChannel::Ptr SshChannelManager::createSftpChannel()
+{
+ SftpChannel::Ptr sftp(new SftpChannel(m_nextLocalChannelId++, m_sendFacility));
+ insertChannel(sftp->d, sftp);
+ return sftp;
+}
+
+void SshChannelManager::insertChannel(AbstractSshChannel *priv,
+ const QSharedPointer<QObject> &pub)
+{
+ m_channels.insert(priv->localChannelId(), priv);
+ m_sessions.insert(priv, pub);
+}
+
+void SshChannelManager::closeAllChannels()
+{
+ for (ChannelIterator it = m_channels.begin(); it != m_channels.end(); ++it)
+ it.value()->closeChannel();
+ m_channels.clear();
+ m_sessions.clear();
+}
+
+void SshChannelManager::removeChannel(ChannelIterator it)
+{
+ Q_ASSERT(it != m_channels.end() && "Unexpected channel lookup failure.");
+ const int removeCount = m_sessions.remove(it.value());
+ Q_ASSERT(removeCount == 1 && "Session for channel not found.");
+ m_channels.erase(it);
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sshchannelmanager_p.h b/src/plugins/coreplugin/ssh/sshchannelmanager_p.h
new file mode 100644
index 0000000000..fe62c00924
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshchannelmanager_p.h
@@ -0,0 +1,89 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHCHANNELLAYER_P_H
+#define SSHCHANNELLAYER_P_H
+
+#include <QtCore/QHash>
+#include <QtCore/QSharedPointer>
+
+namespace Core {
+
+class SftpChannel;
+class SshRemoteProcess;
+
+namespace Internal {
+
+class AbstractSshChannel;
+class SshIncomingPacket;
+class SshSendFacility;
+
+class SshChannelManager
+{
+public:
+ SshChannelManager(SshSendFacility &sendFacility);
+ ~SshChannelManager();
+
+ QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
+ QSharedPointer<SftpChannel> createSftpChannel();
+ void closeAllChannels();
+
+ 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);
+
+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);
+
+ SshSendFacility &m_sendFacility;
+ QHash<quint32, AbstractSshChannel *> m_channels;
+ QHash<AbstractSshChannel *, QSharedPointer<QObject> > m_sessions;
+ quint32 m_nextLocalChannelId;
+};
+
+} // namespace Internal
+} // namespace Core
+
+#endif // SSHCHANNELLAYER_P_H
diff --git a/src/plugins/coreplugin/ssh/sshconnection.cpp b/src/plugins/coreplugin/ssh/sshconnection.cpp
index 7433bbebd8..fbf63d7670 100644
--- a/src/plugins/coreplugin/ssh/sshconnection.cpp
+++ b/src/plugins/coreplugin/ssh/sshconnection.cpp
@@ -1,19 +1,20 @@
-/****************************************************************************
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
-** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
-** This file is part of Qt Creator.
+** Commercial Usage
**
-** $QT_BEGIN_LICENSE:LGPL$
-** No Commercial Usage
-** This file contains pre-release code and may not be distributed.
-** You may use this file in accordance with the terms and conditions
-** contained in the Technology Preview License Agreement accompanying
-** this package.
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
**
** 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 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
@@ -21,471 +22,540 @@
** 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.
-**
-** If you have questions regarding the use of this file, please contact
-** Nokia at qt-info@nokia.com.
-**
-**
-**
-**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
**
-**
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+**************************************************************************/
#include "sshconnection.h"
+#include "sshconnection_p.h"
-#include "ne7sshobject.h"
+#include "sftpchannel.h"
+#include "sshcapabilities_p.h"
+#include "sshchannelmanager_p.h"
+#include "sshcryptofacility_p.h"
+#include "sshexception_p.h"
+#include "sshkeyexchange_p.h"
-#include <QtCore/QCoreApplication>
-#include <QtCore/QDir>
-#include <QtCore/QFileInfo>
-#include <QtCore/QMutex>
-#include <QtCore/QThread>
-#include <QtCore/QWaitCondition>
-
-#include <ne7ssh.h>
+#include <botan/exceptn.h>
+#include <botan/init.h>
-#include <exception>
+#include <QtCore/QFile>
+#include <QtCore/QMutex>
+#include <QtNetwork/QTcpSocket>
namespace Core {
namespace {
+ const QByteArray ClientId("SSH-2.0-QtCreator\r\n");
-class GenericSshConnection
-{
- Q_DECLARE_TR_FUNCTIONS(GenericSshConnection)
-public:
- GenericSshConnection(const SshServerInfo &server)
- : ssh(Internal::Ne7SshObject::instance()->get()),
- m_server(server),
- m_channel(-1)
- { }
-
- ~GenericSshConnection()
- {
- quit();
- }
+ bool staticInitializationsDone = false;
+ QMutex staticInitMutex;
- bool start(bool shell, void (*callbackFunc)(void *), void *callbackArg)
+ void doStaticInitializationsIfNecessary()
{
- Q_ASSERT(m_channel == -1);
-
- try {
- const QString *authString;
- int (ne7ssh::*connFunc)(const char *, int, const char *,
- const char *, bool, int, void (*)(void *), void *);
- if (m_server.authType == SshServerInfo::AuthByPwd) {
- authString = &m_server.pwd;
- connFunc = &ne7ssh::connectWithPassword;
- } else {
- authString = &m_server.privateKeyFile;
- connFunc = &ne7ssh::connectWithKey;
+ if (!staticInitializationsDone) {
+ staticInitMutex.lock();
+ if (!staticInitializationsDone) {
+ Botan::LibraryInitializer::initialize("thread_safe=true");
+ qRegisterMetaType<SshError>("SshError");
+ staticInitializationsDone = true;
}
- m_channel = (ssh.data()->*connFunc)(m_server.host.toLatin1(),
- m_server.port, m_server.uname.toAscii(), authString->toLatin1(),
- shell, m_server.timeout, callbackFunc, callbackArg);
- if (m_channel == -1) {
- setError(tr("Could not connect to host."), false);
- return false;
- }
- } catch (const std::exception &e) {
- // Should in theory not be necessary, but Net7 leaks Botan exceptions.
- setError(tr("Error in cryptography backend: %1")
- .arg(QLatin1String(e.what())), false);
- return false;
- }
-
- return true;
- }
-
- void quit()
- {
- const int channel = m_channel;
- if (channel != -1) {
- m_channel = -1;
- if (!ssh->close(channel))
- qWarning("%s: close() failed.", Q_FUNC_INFO);
+ staticInitMutex.unlock();
}
}
+}
- bool isConnected() const { return channel() != -1; }
- bool hasError() const { return !m_error.isEmpty(); }
- QString error() const { return m_error; }
- int channel() const { return m_channel; }
- QString lastNe7Error() { return ssh->errors()->pop(channel()); }
- const SshServerInfo &server() { return m_server; }
+// TODO: Mechanism for checking the host key. First connection to host: save, later: compare
- void setError(const QString error, bool appendNe7ErrMsg)
- {
- m_error = error;
- if (appendNe7ErrMsg)
- m_error += QLatin1String(": ") + lastNe7Error();
- }
-
- QSharedPointer<ne7ssh> ssh;
-private:
- const SshServerInfo m_server;
- QString m_error;
- int m_channel;
-};
+SshConnection::Ptr SshConnection::create()
+{
+ doStaticInitializationsIfNecessary();
+ return Ptr(new SshConnection);
+}
-char *alloc(size_t n)
+SshConnection::SshConnection() : d(new Internal::SshConnectionPrivate(this))
{
- return new char[n];
+ connect(d, SIGNAL(connected()), this, SIGNAL(connected()),
+ Qt::QueuedConnection);
+ connect(d, SIGNAL(dataAvailable(QString)), this,
+ SIGNAL(dataAvailable(QString)), Qt::QueuedConnection);
+ connect(d, SIGNAL(disconnected()), this, SIGNAL(disconnected()),
+ Qt::QueuedConnection);
+ connect(d, SIGNAL(error(SshError)), this, SIGNAL(error(SshError)),
+ Qt::QueuedConnection);
}
-} // anonymous namespace
+void SshConnection::connectToHost(const SshConnectionParameters &serverInfo)
+{
+ d->connectToHost(serverInfo);
+}
-namespace Internal {
+void SshConnection::disconnectFromHost()
+{
+ d->closeConnection(Internal::SSH_DISCONNECT_BY_APPLICATION, SshNoError, "",
+ QString());
+}
-struct InteractiveSshConnectionPrivate
+SshConnection::State SshConnection::state() const
{
- InteractiveSshConnectionPrivate(const SshServerInfo &server)
- : conn(server), outputReader(0) {}
+ switch (d->state()) {
+ case Internal::SocketUnconnected:
+ return Unconnected;
+ case Internal::ConnectionEstablished:
+ return Connected;
+ default:
+ return Connecting;
+ }
+}
- GenericSshConnection conn;
- ConnectionOutputReader *outputReader;
- QByteArray remoteOutput;
- QMutex mutex;
- QWaitCondition waitCond;
-};
+SshError SshConnection::errorState() const
+{
+ return d->error();
+}
-struct NonInteractiveSshConnectionPrivate
+QString SshConnection::errorString() const
{
- NonInteractiveSshConnectionPrivate(const SshServerInfo &server)
- : conn(server) {}
+ return d->errorString();
+}
- GenericSshConnection conn;
- Ne7SftpSubsystem sftp;
-};
+SshConnectionParameters SshConnection::connectionParameters() const
+{
+ return d->m_connParams;
+}
-class ConnectionOutputReader : public QThread
+SshConnection::~SshConnection()
{
-public:
- ConnectionOutputReader(InteractiveSshConnection *parent)
- : QThread(parent), m_conn(parent), m_stopRequested(false),
- m_dataAvailable(false)
- {}
+ disconnect();
+ disconnectFromHost();
+ delete d;
+}
- ~ConnectionOutputReader()
- {
- stop();
- wait();
- }
+QSharedPointer<SshRemoteProcess> SshConnection::createRemoteProcess(const QByteArray &command)
+{
+ return state() == Connected
+ ? d->createRemoteProcess(command) : QSharedPointer<SshRemoteProcess>();
+}
- void stop()
- {
- m_mutex.lock();
- m_stopRequested = true;
- m_waitCond.wakeOne();
- m_mutex.unlock();
- }
+QSharedPointer<SftpChannel> SshConnection::createSftpChannel()
+{
+ return state() == Connected
+ ? d->createSftpChannel() : QSharedPointer<SftpChannel>();
+}
- void dataAvailable()
- {
- m_mutex.lock();
- m_dataAvailable = true;
- m_waitCond.wakeOne();
- m_mutex.unlock();
- }
-private:
- virtual void run()
- {
- while (true) {
- m_mutex.lock();
- if (m_stopRequested) {
- m_mutex.unlock();
- return;
- }
- const int channel = m_conn->d->conn.channel();
- if (!m_dataAvailable || channel == -1)
- m_waitCond.wait(&m_mutex);
- m_dataAvailable = false;
- m_mutex.unlock();
- QScopedPointer<char, QScopedPointerArrayDeleter<char> >
- output(m_conn->d->conn.ssh->readAndReset(channel, alloc));
- if (output) {
- m_conn->d->mutex.lock();
- m_conn->d->remoteOutput += output.data();
- emit m_conn->remoteOutputAvailable();
- m_conn->d->mutex.unlock();
- }
- }
- }
+namespace Internal {
- InteractiveSshConnection *m_conn;
- bool m_stopRequested;
- bool m_dataAvailable;
- QMutex m_mutex;
- QWaitCondition m_waitCond;
-};
+SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn)
+ : m_socket(new QTcpSocket(this)), m_state(SocketUnconnected),
+ m_sendFacility(m_socket),
+ m_channelManager(new SshChannelManager(m_sendFacility)),
+ m_error(SshNoError), m_ignoreNextPacket(false), m_conn(conn)
+{
+ setupPacketHandlers();
+ connect(&m_timeoutTimer, SIGNAL(timeout()), this, SLOT(handleTimeout()));
+}
-} // namespace Internal
+SshConnectionPrivate::~SshConnectionPrivate()
+{
+ disconnect();
+}
+void SshConnectionPrivate::setupPacketHandlers()
+{
+ typedef SshConnectionPrivate This;
+
+ setupPacketHandler(SSH_MSG_KEXINIT, StateList() << SocketConnected,
+ &This::handleKeyExchangeInitPacket);
+ setupPacketHandler(SSH_MSG_KEXDH_REPLY, StateList() << KeyExchangeStarted,
+ &This::handleKeyExchangeReplyPacket);
+
+ setupPacketHandler(SSH_MSG_NEWKEYS, StateList() << KeyExchangeSuccess,
+ &This::handleNewKeysPacket);
+ setupPacketHandler(SSH_MSG_SERVICE_ACCEPT,
+ StateList() << UserAuthServiceRequested,
+ &This::handleServiceAcceptPacket);
+ 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);
+
+ 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
+ << KeyExchangeStarted << KeyExchangeSuccess
+ << UserAuthServiceRequested << UserAuthRequested
+ << ConnectionEstablished, &This::handleDisconnect);
+}
-namespace {
+void SshConnectionPrivate::setupPacketHandler(SshPacketType type,
+ const SshConnectionPrivate::StateList &states,
+ SshConnectionPrivate::PacketHandler handler)
+{
+ m_packetHandlers.insert(type, HandlerInStates(states, handler));
+}
-void wakeupReader(void *opaqueReader)
+void SshConnectionPrivate::handleSocketConnected()
{
- static_cast<Internal::ConnectionOutputReader*>(opaqueReader)->dataAvailable();
+ m_state = SocketConnected;
+ sendData(ClientId);
}
-} // Anonymous namespace
+void SshConnectionPrivate::handleIncomingData()
+{
+ if (m_state == SocketUnconnected)
+ return; // For stuff queued in the event loop after we've called closeConnection();
+
+ try {
+ m_incomingData += m_socket->readAll();
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("state = %d, remote data size = %d", m_state,
+ m_incomingData.count());
+#endif
+ if (m_state == SocketConnected)
+ handleServerId();
+ handlePackets();
+ } catch (SshServerException &e) {
+ closeConnection(e.error, SshProtocolError, e.errorStringServer,
+ tr("SSH Protocol error: %1").arg(e.errorStringUser));
+ } catch (SshClientException &e) {
+ closeConnection(SSH_DISCONNECT_BY_APPLICATION, e.error, "",
+ e.errorString);
+ } catch (Botan::Exception &e) {
+ closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshInternalError, "",
+ tr("Botan exception: %1").arg(e.what()));
+ }
+}
+void SshConnectionPrivate::handleServerId()
+{
+ const int idOffset = m_incomingData.indexOf("SSH-");
+ if (idOffset == -1)
+ return;
+ m_incomingData.remove(0, idOffset);
+ if (m_incomingData.size() < 7)
+ return;
+ const QByteArray &version = m_incomingData.mid(4, 3);
+ if (version != "2.0") {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
+ "Invalid protocol version.",
+ tr("Invalid protocol version: Expected '2.0', got '%1'.")
+ .arg(SshPacketParser::asUserString(version)));
+ }
+ const int endOffset = m_incomingData.indexOf("\r\n");
+ if (endOffset == -1)
+ return;
+ if (m_incomingData.at(7) != '-') {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Invalid server id.", tr("Invalid server id '%1'.")
+ .arg(SshPacketParser::asUserString(m_incomingData)));
+ }
-InteractiveSshConnection::InteractiveSshConnection(const SshServerInfo &server)
- : d(new Internal::InteractiveSshConnectionPrivate(server))
-{
- d->outputReader = new Internal::ConnectionOutputReader(this);
+ m_keyExchange.reset(new SshKeyExchange(m_sendFacility));
+ m_keyExchange->sendKexInitPacket(m_incomingData.left(endOffset));
+ m_incomingData.remove(0, endOffset + 2);
}
-InteractiveSshConnection::~InteractiveSshConnection()
+void SshConnectionPrivate::handlePackets()
{
- d->conn.ssh->send("exit\n", d->conn.channel());
- quit();
- delete d;
+ m_incomingPacket.consumeData(m_incomingData);
+ while (m_incomingPacket.isComplete()) {
+ handleCurrentPacket();
+ m_incomingPacket.clear();
+ m_incomingPacket.consumeData(m_incomingData);
+ }
}
-bool InteractiveSshConnection::start()
+void SshConnectionPrivate::handleCurrentPacket()
{
- if (isConnected())
- return true;
+ Q_ASSERT(m_incomingPacket.isComplete());
+ Q_ASSERT(m_state == KeyExchangeStarted || !m_ignoreNextPacket);
- if (!d->conn.start(true, wakeupReader, d->outputReader))
- return false;
+ if (m_ignoreNextPacket) {
+ m_ignoreNextPacket = false;
+ return;
+ }
- d->outputReader->start();
- return true;
+ QHash<SshPacketType, HandlerInStates>::ConstIterator it
+ = m_packetHandlers.find(m_incomingPacket.type());
+ if (it == m_packetHandlers.end()) {
+ m_sendFacility.sendMsgUnimplementedPacket(m_incomingPacket.serverSeqNr());
+ return;
+ }
+ if (!it.value().first.contains(m_state)) {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected packet.", tr("Unexpected packet of type %1.")
+ .arg(m_incomingPacket.type()));
+ }
+ (this->*it.value().second)();
}
-bool InteractiveSshConnection::sendInput(const QByteArray &input)
+void SshConnectionPrivate::handleKeyExchangeInitPacket()
{
- if (!d->conn.ssh->send(input.data(), d->conn.channel())) {
- d->conn.setError(tr("Error sending input"), true);
- return false;
- }
- return true;
+ // If the server sends a guessed packet, the guess must be wrong,
+ // because the algorithms we support requires us to initiate the
+ // key exchange.
+ if (m_keyExchange->sendDhInitPacket(m_incomingPacket))
+ m_ignoreNextPacket = true;
+ m_state = KeyExchangeStarted;
}
-void InteractiveSshConnection::quit()
+void SshConnectionPrivate::handleKeyExchangeReplyPacket()
{
- d->mutex.lock();
- d->waitCond.wakeOne();
- d->mutex.unlock();
- d->outputReader->stop();
- d->conn.quit();
+ m_keyExchange->sendNewKeysPacket(m_incomingPacket,
+ ClientId.left(ClientId.size() - 2));
+ m_sendFacility.recreateKeys(*m_keyExchange);
+ m_state = KeyExchangeSuccess;
}
-QByteArray InteractiveSshConnection::waitForRemoteOutput(int msecs)
+void SshConnectionPrivate::handleNewKeysPacket()
{
- d->mutex.lock();
- if (d->remoteOutput.isEmpty())
- d->waitCond.wait(&d->mutex, msecs == -1 ? ULONG_MAX : msecs);
- const QByteArray remoteOutput = d->remoteOutput;
- d->remoteOutput.clear();
- d->mutex.unlock();
- return remoteOutput;
+ m_incomingPacket.recreateKeys(*m_keyExchange);
+ m_keyExchange.reset();
+ m_sendFacility.sendUserAuthServiceRequestPacket();
+ m_state = UserAuthServiceRequested;
}
+void SshConnectionPrivate::handleServiceAcceptPacket()
+{
+ if (m_connParams.authType == SshConnectionParameters::AuthByPwd) {
+ m_sendFacility.sendUserAuthByPwdRequestPacket(m_connParams.uname.toUtf8(),
+ SshCapabilities::SshConnectionService, m_connParams.pwd.toUtf8());
+ } else {
+ QFile privKeyFile(m_connParams.privateKeyFile);
+ bool couldOpen = privKeyFile.open(QIODevice::ReadOnly);
+ QByteArray contents;
+ if (couldOpen)
+ contents = privKeyFile.readAll();
+ if (!couldOpen || privKeyFile.error() != QFile::NoError) {
+ throw SshClientException(SshKeyFileError,
+ tr("Could not read private key file: %1")
+ .arg(privKeyFile.errorString()));
+ }
-InteractiveSshConnection::Ptr InteractiveSshConnection::create(const SshServerInfo &server)
-{
- return Ptr(new InteractiveSshConnection(server));
+ m_sendFacility.createAuthenticationKey(contents);
+ m_sendFacility.sendUserAuthByKeyRequestPacket(m_connParams.uname.toUtf8(),
+ SshCapabilities::SshConnectionService);
+ }
+ m_state = UserAuthRequested;
}
-bool InteractiveSshConnection::isConnected() const
+void SshConnectionPrivate::handlePasswordExpiredPacket()
{
- return d->conn.isConnected();
+ if (m_connParams.authType == SshConnectionParameters::AuthByKey) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Got SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, but did not use password.");
+ }
+
+ throw SshClientException(SshAuthenticationError, tr("Password expired."));
}
-bool InteractiveSshConnection::hasError() const
+void SshConnectionPrivate::handleUserAuthBannerPacket()
{
- return d->conn.hasError();
+ emit dataAvailable(m_incomingPacket.extractUserAuthBanner().message);
}
-QString InteractiveSshConnection::error() const
+void SshConnectionPrivate::handleGlobalRequest()
{
- return d->conn.error();
+ m_sendFacility.sendRequestFailurePacket();
}
-
-namespace {
-
-class FileMgr
+void SshConnectionPrivate::handleUserAuthSuccessPacket()
{
-public:
- FileMgr(const QString &filePath, const char *mode)
- : m_file(fopen(filePath.toLatin1().data(), mode)) {}
- ~FileMgr() { if (m_file) fclose(m_file); }
- FILE *file() const { return m_file; }
-private:
- FILE * const m_file;
-};
-
-} // Anonymous namespace
+ m_state = ConnectionEstablished;
+ m_timeoutTimer.stop();
+ emit connected();
+}
-SftpConnection::SftpConnection(const SshServerInfo &server)
- : d(new Internal::NonInteractiveSshConnectionPrivate(server))
-{ }
+void SshConnectionPrivate::handleUserAuthFailurePacket()
+{
+ const QString errorMsg = m_connParams.authType == SshConnectionParameters::AuthByPwd
+ ? tr("Server rejected password.") : tr("Server rejected key.");
+ throw SshClientException(SshAuthenticationError, errorMsg);
+}
+void SshConnectionPrivate::handleDebugPacket()
+{
+ const SshDebug &msg = m_incomingPacket.extractDebug();
+ if (msg.display)
+ emit dataAvailable(msg.message);
+}
-SftpConnection::~SftpConnection()
+void SshConnectionPrivate::handleChannelRequest()
{
- quit();
- delete d;
+ m_channelManager->handleChannelRequest(m_incomingPacket);
}
-bool SftpConnection::start()
+void SshConnectionPrivate::handleChannelOpen()
{
- if (isConnected())
- return true;
- if (!d->conn.start(false, 0, 0))
- return false;
- if (!d->conn.ssh->initSftp(d->sftp, d->conn.channel())
- || !d->sftp.setTimeout(d->conn.server().timeout)) {
- d->conn.setError(tr("Error setting up SFTP subsystem"), true);
- quit();
- return false;
- }
- return true;
+ m_channelManager->handleChannelOpen(m_incomingPacket);
}
-bool SftpConnection::transferFiles(const QList<SftpTransferInfo> &transferList)
+void SshConnectionPrivate::handleChannelOpenFailure()
{
- for (int i = 0; i < transferList.count(); ++i) {
- const SftpTransferInfo &transfer = transferList.at(i);
- bool success;
- if (transfer.type == SftpTransferInfo::Upload) {
- success = upload(transfer.localFilePath, transfer.remoteFilePath);
- } else {
- success = download(transfer.remoteFilePath, transfer.localFilePath);
- }
- if (!success)
- return false;
- }
+ m_channelManager->handleChannelOpenFailure(m_incomingPacket);
+}
- return true;
+void SshConnectionPrivate::handleChannelOpenConfirmation()
+{
+ m_channelManager->handleChannelOpenConfirmation(m_incomingPacket);
}
-bool SftpConnection::upload(const QString &localFilePath,
- const QByteArray &remoteFilePath)
+void SshConnectionPrivate::handleChannelSuccess()
{
- FileMgr fileMgr(localFilePath, "rb");
- if (!fileMgr.file()) {
- d->conn.setError(tr("Could not open file '%1'").arg(localFilePath),
- false);
- return false;
- }
+ m_channelManager->handleChannelSuccess(m_incomingPacket);
+}
- if (!d->sftp.put(fileMgr.file(), remoteFilePath.data())) {
- d->conn.setError(tr("Could not uplodad file '%1'")
- .arg(localFilePath), true);
- return false;
- }
+void SshConnectionPrivate::handleChannelFailure()
+{
+ m_channelManager->handleChannelFailure(m_incomingPacket);
+}
- emit fileCopied(localFilePath);
- return true;
+void SshConnectionPrivate::handleChannelWindowAdjust()
+{
+ m_channelManager->handleChannelWindowAdjust(m_incomingPacket);
}
-bool SftpConnection::download(const QByteArray &remoteFilePath,
- const QString &localFilePath)
+void SshConnectionPrivate::handleChannelData()
{
- FileMgr fileMgr(localFilePath, "wb");
- if (!fileMgr.file()) {
- d->conn.setError(tr("Could not open file '%1'").arg(localFilePath),
- false);
- return false;
- }
+ m_channelManager->handleChannelData(m_incomingPacket);
+}
- if (!d->sftp.get(remoteFilePath.data(), fileMgr.file())) {
- d->conn.setError(tr("Could not copy remote file '%1' to local file '%2'")
- .arg(remoteFilePath, localFilePath), false);
- return false;
- }
+void SshConnectionPrivate::handleChannelExtendedData()
+{
+ m_channelManager->handleChannelExtendedData(m_incomingPacket);
+}
- emit fileCopied(remoteFilePath);
- return true;
+void SshConnectionPrivate::handleChannelEof()
+{
+ m_channelManager->handleChannelEof(m_incomingPacket);
}
-bool SftpConnection::createRemoteDir(const QByteArray &remoteDir)
+void SshConnectionPrivate::handleChannelClose()
{
- if (!d->sftp.mkdir(remoteDir.data())) {
- d->conn.setError(tr("Could not create remote directory"), true);
- return false;
- }
- return true;
+ m_channelManager->handleChannelClose(m_incomingPacket);
}
-bool SftpConnection::removeRemoteDir(const QByteArray &remoteDir)
+void SshConnectionPrivate::handleDisconnect()
{
- if (!d->sftp.rmdir(remoteDir.data())) {
- d->conn.setError(tr("Could not remove remote directory"), true);
- return false;
- }
- return true;
+ const SshDisconnect msg = m_incomingPacket.extractDisconnect();
+ throw SshServerException(SSH_DISCONNECT_CONNECTION_LOST,
+ "", tr("Server closed connection: %1").arg(msg.description));
}
-QByteArray SftpConnection::listRemoteDirContents(const QByteArray &remoteDir,
- bool withAttributes, bool &ok)
+void SshConnectionPrivate::sendData(const QByteArray &data)
{
- const char * const buffer = d->sftp.ls(remoteDir.data(), withAttributes);
- if (!buffer) {
- d->conn.setError(tr("Could not get remote directory contents"), true);
- ok = false;
- return QByteArray();
- }
- ok = true;
- return QByteArray(buffer);
+ m_socket->write(data);
}
-bool SftpConnection::removeRemoteFile(const QByteArray &remoteFile)
+void SshConnectionPrivate::handleSocketDisconnected()
{
- if (!d->sftp.rm(remoteFile.data())) {
- d->conn.setError(tr("Could not remove remote file"), true);
- return false;
- }
- return true;
+ closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError,
+ "Connection closed unexpectedly.",
+ tr("Connection closed unexpectedly."));
}
-bool SftpConnection::changeRemoteWorkingDir(const QByteArray &newRemoteDir)
+void SshConnectionPrivate::handleSocketError()
{
- if (!d->sftp.cd(newRemoteDir.data())) {
- d->conn.setError(tr("Could not change remote working directory"), true);
- return false;
+ if (m_error == SshNoError) {
+ closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshSocketError,
+ "Network error", m_socket->errorString());
}
- return true;
}
-void SftpConnection::quit()
+void SshConnectionPrivate::handleTimeout()
{
- d->conn.quit();
+ if (m_state != ConnectionEstablished)
+ closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "",
+ tr("Connection timed out."));
}
-bool SftpConnection::isConnected() const
-{
- return d->conn.isConnected();
+void SshConnectionPrivate::connectToHost(const SshConnectionParameters &serverInfo)
+{
+ m_incomingData.clear();
+ m_incomingPacket.reset();
+ m_sendFacility.reset();
+ m_error = SshNoError;
+ m_ignoreNextPacket = false;
+ m_errorString.clear();
+ connect(m_socket, SIGNAL(connected()), this, SLOT(handleSocketConnected()));
+ connect(m_socket, SIGNAL(readyRead()), this, SLOT(handleIncomingData()));
+ connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this,
+ SLOT(handleSocketError()));
+ connect(m_socket, SIGNAL(disconnected()), this,
+ SLOT(handleSocketDisconnected()));
+ this->m_connParams = serverInfo;
+ m_state = SocketConnecting;
+ m_timeoutTimer.start(m_connParams.timeout * 1000);
+ m_socket->connectToHost(serverInfo.host, serverInfo.port);
}
-bool SftpConnection::hasError() const
-{
- return d->conn.hasError();
+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, 0, this, 0);
+ try {
+ m_channelManager->closeAllChannels();
+ m_sendFacility.sendDisconnectPacket(sshError, serverErrorString);
+ } catch (Botan::Exception &) {} // Nothing sensible to be done here.
+ if (m_error != SshNoError)
+ emit error(userError);
+ if (m_state == ConnectionEstablished)
+ emit disconnected();
+ m_socket->disconnectFromHost();
+ m_state = SocketUnconnected;
}
-QString SftpConnection::error() const
+QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)
{
- return d->conn.error();
+ return m_channelManager->createRemoteProcess(command);
}
-SftpConnection::Ptr SftpConnection::create(const SshServerInfo &server)
+QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
{
- return Ptr(new SftpConnection(server));
+ return m_channelManager->createSftpChannel();
}
+} // namespace Internal
} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sshconnection.h b/src/plugins/coreplugin/ssh/sshconnection.h
index 8c7b59f594..e7f73995a4 100644
--- a/src/plugins/coreplugin/ssh/sshconnection.h
+++ b/src/plugins/coreplugin/ssh/sshconnection.h
@@ -1,19 +1,20 @@
-/****************************************************************************
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
-** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
-** This file is part of Qt Creator.
+** Commercial Usage
**
-** $QT_BEGIN_LICENSE:LGPL$
-** No Commercial Usage
-** This file contains pre-release code and may not be distributed.
-** You may use this file in accordance with the terms and conditions
-** contained in the Technology Preview License Agreement accompanying
-** this package.
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
**
** 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 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
@@ -21,27 +22,16 @@
** 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.
-**
-** If you have questions regarding the use of this file, please contact
-** Nokia at qt-info@nokia.com.
-**
-**
-**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
**
-**
-**
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+**************************************************************************/
#ifndef SSHCONNECTION_H
#define SSHCONNECTION_H
+#include "ssherrors.h"
+
#include <coreplugin/core_global.h>
#include <QtCore/QByteArray>
@@ -50,14 +40,14 @@
#include <QtCore/QString>
namespace Core {
+class SftpChannel;
+class SshRemoteProcess;
namespace Internal {
- struct InteractiveSshConnectionPrivate;
- struct NonInteractiveSshConnectionPrivate;
- class ConnectionOutputReader;
-}
+class SshConnectionPrivate;
+} // namespace Internal
-struct CORE_EXPORT SshServerInfo
+struct CORE_EXPORT SshConnectionParameters
{
QString host;
QString uname;
@@ -69,85 +59,44 @@ struct CORE_EXPORT SshServerInfo
};
-class CORE_EXPORT InteractiveSshConnection : public QObject
+/*
+ * This class provides an SSH connection, implementing protocol version 2.0
+ * It can spawn channels for remote execution and SFTP operations (version 3).
+ * It operates asynchronously (non-blocking) and is not thread-safe.
+ */
+class CORE_EXPORT SshConnection : public QObject
{
Q_OBJECT
- Q_DISABLE_COPY(InteractiveSshConnection)
- friend class Internal::ConnectionOutputReader;
+ Q_DISABLE_COPY(SshConnection)
public:
- typedef QSharedPointer<InteractiveSshConnection> Ptr;
+ enum State { Unconnected, Connecting, Connected };
+ typedef QSharedPointer<SshConnection> Ptr;
- static Ptr create(const SshServerInfo &server);
+ static Ptr create();
- bool start();
- void quit();
- bool isConnected() const;
- bool sendInput(const QByteArray &input); // Should normally end in newline.
- QByteArray waitForRemoteOutput(int msecs = -1);
- bool hasError() const;
- QString error() const;
- ~InteractiveSshConnection();
+ void connectToHost(const SshConnectionParameters &serverInfo);
+ void disconnectFromHost();
+ State state() const;
+ SshError errorState() const;
+ QString errorString() const;
+ SshConnectionParameters connectionParameters() const;
+ ~SshConnection();
-signals:
- void remoteOutputAvailable();
-
-private:
- InteractiveSshConnection(const SshServerInfo &server);
-
- struct Internal::InteractiveSshConnectionPrivate *d;
-};
-
-
-struct CORE_EXPORT SftpTransferInfo
-{
- enum Type { Upload, Download };
-
- SftpTransferInfo(const QString &localFilePath,
- const QByteArray &remoteFilePath, Type type)
- : localFilePath(localFilePath),
- remoteFilePath(remoteFilePath),
- type(type)
- {
- }
-
- QString localFilePath;
- QByteArray remoteFilePath;
- Type type;
-};
-
-class CORE_EXPORT SftpConnection : public QObject
-{
- Q_OBJECT
- Q_DISABLE_COPY(SftpConnection)
-public:
- typedef QSharedPointer<SftpConnection> Ptr;
-
- static Ptr create(const SshServerInfo &server);
- bool start();
- void quit();
- bool isConnected() const;
- bool hasError() const;
- QString error() const;
- bool upload(const QString &localFilePath, const QByteArray &remoteFilePath);
- bool download(const QByteArray &remoteFilePath, const QString &localFilePath);
- bool transferFiles(const QList<SftpTransferInfo> &transferList);
- bool createRemoteDir(const QByteArray &remoteDir);
- bool removeRemoteDir(const QByteArray &remoteDir);
- bool removeRemoteFile(const QByteArray &remoteFile);
- bool changeRemoteWorkingDir(const QByteArray &newRemoteDir);
- QByteArray listRemoteDirContents(const QByteArray &remoteDir,
- bool withAttributes, bool &ok);
- ~SftpConnection();
+ QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
+ QSharedPointer<SftpChannel> createSftpChannel();
signals:
- void fileCopied(const QString &filePath);
+ void connected();
+ void disconnected();
+ void dataAvailable(const QString &message);
+ void error(SshError);
private:
- SftpConnection(const SshServerInfo &server);
+ SshConnection();
- Internal::NonInteractiveSshConnectionPrivate *d;
+ Internal::SshConnectionPrivate *d;
};
-} // namespace Core
+} // namespace Internal
#endif // SSHCONNECTION_H
diff --git a/src/plugins/coreplugin/ssh/sshconnection_p.h b/src/plugins/coreplugin/ssh/sshconnection_p.h
new file mode 100644
index 0000000000..c20ccf78b5
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshconnection_p.h
@@ -0,0 +1,157 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHCONNECTION_P_H
+#define SSHCONNECTION_P_H
+
+#include "sshconnection.h"
+#include "sshexception_p.h"
+#include "sshincomingpacket_p.h"
+#include "sshremoteprocess.h"
+#include "sshsendfacility_p.h"
+
+#include <QtCore/QHash>
+#include <QtCore/QList>
+#include <QtCore/QObject>
+#include <QtCore/QPair>
+#include <QtCore/QScopedPointer>
+#include <QtCore/QTimer>
+
+QT_BEGIN_NAMESPACE
+class QTcpSocket;
+QT_END_NAMESPACE
+
+namespace Botan { class Exception; }
+
+namespace Core {
+class SftpChannel;
+
+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
+ KeyExchangeStarted, // After server's KEXINIT message
+ KeyExchangeSuccess, // After server's DH_REPLY message
+ UserAuthServiceRequested,
+ UserAuthRequested,
+
+ ConnectionEstablished // After service has been started
+ // ...
+};
+
+class SshConnectionPrivate : public QObject
+{
+ Q_OBJECT
+ friend class Core::SshConnection;
+public:
+ SshConnectionPrivate(SshConnection *conn);
+ ~SshConnectionPrivate();
+
+ void connectToHost(const SshConnectionParameters &serverInfo);
+ void closeConnection(SshErrorCode sshError, SshError userError,
+ const QByteArray &serverErrorString, const QString &userErrorString);
+ QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
+ QSharedPointer<SftpChannel> createSftpChannel();
+ SshStateInternal state() const { return m_state; }
+ SshError error() const { return m_error; }
+ QString errorString() const { return m_errorString; }
+
+signals:
+ void connected();
+ void disconnected();
+ void dataAvailable(const QString &message);
+ void error(SshError);
+
+private:
+ Q_SLOT void handleSocketConnected();
+ Q_SLOT void handleIncomingData();
+ Q_SLOT void handleSocketError();
+ Q_SLOT void handleSocketDisconnected();
+ Q_SLOT void handleTimeout();
+
+ void handleServerId();
+ void handlePackets();
+ void handleCurrentPacket();
+ void handleKeyExchangeInitPacket();
+ void handleKeyExchangeReplyPacket();
+ void handleNewKeysPacket();
+ void handleServiceAcceptPacket();
+ void handlePasswordExpiredPacket();
+ void handleUserAuthSuccessPacket();
+ void handleUserAuthFailurePacket();
+ void handleUserAuthBannerPacket();
+ void handleGlobalRequest();
+ void handleDebugPacket();
+ 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 sendData(const QByteArray &data);
+
+ 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;
+
+ QTcpSocket *m_socket;
+ SshStateInternal m_state;
+ SshIncomingPacket m_incomingPacket;
+ SshSendFacility m_sendFacility;
+ QScopedPointer<SshChannelManager> m_channelManager;
+ SshConnectionParameters m_connParams;
+ QByteArray m_incomingData;
+ SshError m_error;
+ QString m_errorString;
+ QScopedPointer<SshKeyExchange> m_keyExchange;
+ QTimer m_timeoutTimer;
+ bool m_ignoreNextPacket;
+ SshConnection *m_conn;
+};
+
+} // namespace Internal
+} // namespace Core
+
+#endif // SSHCONNECTION_P_H
diff --git a/src/plugins/coreplugin/ssh/sshcryptofacility.cpp b/src/plugins/coreplugin/ssh/sshcryptofacility.cpp
new file mode 100644
index 0000000000..fd2fe32044
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshcryptofacility.cpp
@@ -0,0 +1,369 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshcryptofacility_p.h"
+
+#include "sshbotanconversions_p.h"
+#include "sshcapabilities_p.h"
+#include "sshexception_p.h"
+#include "sshkeyexchange_p.h"
+#include "sshpacket_p.h"
+
+#include <botan/ber_dec.h>
+#include <botan/botan.h>
+#include <botan/cbc.h>
+#include <botan/dsa.h>
+#include <botan/hash.h>
+#include <botan/hmac.h>
+#include <botan/look_pk.h>
+#include <botan/pipe.h>
+#include <botan/pkcs8.h>
+#include <botan/pubkey.h>
+#include <botan/rsa.h>
+
+#include <QtCore/QDebug>
+#include <QtCore/QList>
+
+#include <string>
+
+using namespace Botan;
+
+namespace Core {
+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(0);
+ m_hMac.reset(0);
+}
+
+void SshAbstractCryptoFacility::recreateKeys(const SshKeyExchange &kex)
+{
+ checkInvariant();
+
+ if (m_sessionId.isEmpty())
+ m_sessionId = kex.h();
+ Algorithm_Factory &af = global_state().algorithm_factory();
+ const std::string &cryptAlgo = botanCryptAlgoName(cryptAlgoName(kex));
+ BlockCipher * const cipher = af.prototype_block_cipher(cryptAlgo)->clone();
+
+ m_cipherBlockSize = cipher->BLOCK_SIZE;
+ const QByteArray ivData = generateHash(kex, ivChar(), m_cipherBlockSize);
+ const InitializationVector iv(convertByteArray(ivData), m_cipherBlockSize);
+
+ const quint32 keySize = max_keylength_of(cryptAlgo);
+ const QByteArray cryptKeyData = generateHash(kex, keyChar(), keySize);
+ SymmetricKey cryptKey(convertByteArray(cryptKeyData), keySize);
+
+ BlockCipherMode * const cipherMode
+ = makeCipherMode(cipher, new Null_Padding, iv, cryptKey);
+ m_pipe.reset(new Pipe(cipherMode));
+
+ m_macLength = botanHMacKeyLen(hMacAlgoName(kex));
+ const QByteArray hMacKeyData = generateHash(kex, macChar(), macLength());
+ SymmetricKey hMacKey(convertByteArray(hMacKeyData), macLength());
+ const HashFunction * const hMacProto
+ = af.prototype_hash_function(botanHMacAlgoName(hMacAlgoName(kex)));
+ m_hMac.reset(new HMAC(hMacProto->clone()));
+ 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);
+ quint32 bytesRead = m_pipe->read(reinterpret_cast<byte *>(data.data()) + offset,
+ dataSize, m_pipe->message_count() - 1); // Can't use Pipe::LAST_MESSAGE because of a VC bug.
+ Q_ASSERT(bytesRead == dataSize);
+}
+
+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) {
+ SecureVector<byte> tmpKey;
+ tmpKey.append(convertByteArray(k), k.size());
+ tmpKey.append(convertByteArray(h), h.size());
+ tmpKey.append(key);
+ key.append(kex.hash()->process(tmpKey));
+ }
+ return QByteArray(reinterpret_cast<const char *>(key.begin()), 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-----");
+
+QByteArray SshEncryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
+{
+ return kex.encryptionAlgo();
+}
+
+QByteArray SshEncryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
+{
+ return kex.hMacAlgoClientToServer();
+}
+
+BlockCipherMode *SshEncryptionFacility::makeCipherMode(BlockCipher *cipher,
+ BlockCipherModePaddingMethod *paddingMethod, const InitializationVector &iv,
+ const SymmetricKey &key)
+{
+ return new CBC_Encryption(cipher, paddingMethod, key, iv);
+}
+
+void SshEncryptionFacility::encrypt(QByteArray &data) const
+{
+ convert(data, 0, data.size());
+}
+
+void SshEncryptionFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
+{
+ if (privKeyFileContents == m_cachedPrivKeyContents)
+ return;
+
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("%s: Key not cached, reading", Q_FUNC_INFO);
+#endif
+ QList<BigInt> pubKeyParams;
+ QList<BigInt> allKeyParams;
+ try {
+ createAuthenticationKeyFromPKCS8(privKeyFileContents, pubKeyParams,
+ allKeyParams);
+ } catch (Botan::Exception &) {
+ createAuthenticationKeyFromOpenSSL(privKeyFileContents, pubKeyParams,
+ allKeyParams);
+ }
+
+ foreach (const BigInt &b, allKeyParams) {
+ if (b.is_zero()) {
+ throw SshClientException(SshKeyFileError,
+ SSH_TR("Decoding of private key file failed."));
+ }
+ }
+
+ m_authPubKeyBlob = AbstractSshPacket::encodeString(m_authKeyAlgoName);
+ foreach (const BigInt &b, pubKeyParams)
+ m_authPubKeyBlob += AbstractSshPacket::encodeMpInt(b);
+ m_cachedPrivKeyContents = privKeyFileContents;
+}
+
+void SshEncryptionFacility::createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
+ QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams)
+{
+ Pipe pipe;
+ pipe.process_msg(convertByteArray(privKeyFileContents),
+ privKeyFileContents.size());
+ Private_Key * const key = PKCS8::load_key(pipe, m_rng);
+ if (DSA_PrivateKey * const dsaKey = dynamic_cast<DSA_PrivateKey *>(key)) {
+ m_authKey.reset(dsaKey);
+ pubKeyParams << dsaKey->group_p() << dsaKey->group_q()
+ << dsaKey->group_g() << dsaKey->get_y();
+ allKeyParams << pubKeyParams << dsaKey->get_x();
+ } else if (RSA_PrivateKey * const rsaKey = dynamic_cast<RSA_PrivateKey *>(key)) {
+ m_authKey.reset(rsaKey);
+ pubKeyParams << rsaKey->get_e() << rsaKey->get_n();
+ allKeyParams << pubKeyParams << rsaKey->get_p() << rsaKey->get_q()
+ << rsaKey->get_d();
+ } else {
+ throw Botan::Exception();
+ }
+}
+
+void SshEncryptionFacility::createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
+ QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams)
+{
+ 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 {
+ syntaxOk = false;
+ }
+ if (!syntaxOk) {
+ throw SshClientException(SshKeyFileError,
+ SSH_TR("Private key file has unexpected format."));
+ }
+
+ 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);
+ quint32 version;
+ sequence.decode (version);
+ if (version != 0) {
+ throw SshClientException(SshKeyFileError,
+ SSH_TR("Private key encoding has version %1, expected 0.")
+ .arg(version));
+ }
+
+ 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 {
+ BigInt p, q, e, d, n;
+ sequence.decode (n).decode (e).decode (d).decode (p).decode (q);
+ RSA_PrivateKey * const rsaKey
+ = new RSA_PrivateKey (m_rng, p, q, e, d, n);
+ m_authKey.reset(rsaKey);
+ pubKeyParams << e << n;
+ allKeyParams << pubKeyParams << p << q << d;
+ }
+
+ sequence.discard_remaining();
+ sequence.verify_end();
+}
+
+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(get_pk_signer (*m_authKey,
+ botanEmsaAlgoName(m_authKeyAlgoName)));
+ QByteArray dataToSign = AbstractSshPacket::encodeString(sessionId()) + data;
+ QByteArray signature
+ = convertByteArray(signer->sign_message(convertByteArray(dataToSign),
+ dataToSign.size(), m_rng));
+ 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();
+}
+
+BlockCipherMode *SshDecryptionFacility::makeCipherMode(BlockCipher *cipher,
+ BlockCipherModePaddingMethod *paddingMethod, const InitializationVector &iv,
+ const SymmetricKey &key)
+{
+ return new CBC_Decryption(cipher, paddingMethod, key, iv);
+}
+
+void SshDecryptionFacility::decrypt(QByteArray &data, quint32 offset,
+ quint32 dataSize) const
+{
+ convert(data, offset, dataSize);
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Decrypted data:");
+ const char * const start = data.constData() + offset;
+ const char * const end = start + dataSize;
+ for (const char *c = start; c < end; ++c)
+ qDebug() << "'" << *c << "' (0x" << (static_cast<int>(*c) & 0xff) << ")";
+#endif
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sshcryptofacility_p.h b/src/plugins/coreplugin/ssh/sshcryptofacility_p.h
new file mode 100644
index 0000000000..f60e6d4b8d
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshcryptofacility_p.h
@@ -0,0 +1,154 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHABSTRACTCRYPTOFACILITY_P_H
+#define SSHABSTRACTCRYPTOFACILITY_P_H
+
+#include <botan/auto_rng.h>
+#include <botan/symkey.h>
+
+#include <QtCore/QByteArray>
+#include <QtCore/QScopedPointer>
+
+namespace Botan {
+ class BigInt;
+ class BlockCipher;
+ class BlockCipherMode;
+ class BlockCipherModePaddingMethod;
+ class HashFunction;
+ class HMAC;
+ class Pipe;
+ class PK_Signing_Key;
+}
+
+namespace Core {
+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; }
+
+protected:
+ SshAbstractCryptoFacility();
+ void convert(QByteArray &data, quint32 offset, quint32 dataSize) const;
+ QByteArray sessionId() const { return m_sessionId; }
+
+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::BlockCipherMode *makeCipherMode(Botan::BlockCipher *cipher,
+ Botan::BlockCipherModePaddingMethod *paddingMethod,
+ const Botan::InitializationVector &iv,
+ const Botan::SymmetricKey &key)=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;
+
+ QByteArray m_sessionId;
+ QScopedPointer<Botan::Pipe> m_pipe;
+ QScopedPointer<Botan::HMAC> 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:
+ virtual QByteArray cryptAlgoName(const SshKeyExchange &kex) const;
+ virtual QByteArray hMacAlgoName(const SshKeyExchange &kex) const;
+ virtual Botan::BlockCipherMode *makeCipherMode(Botan::BlockCipher *cipher,
+ Botan::BlockCipherModePaddingMethod *paddingMethod,
+ const Botan::InitializationVector &iv, const Botan::SymmetricKey &key);
+ virtual char ivChar() const { return 'A'; }
+ virtual char keyChar() const { return 'C'; }
+ virtual char macChar() const { return 'E'; }
+
+ void createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
+ QList<Botan::BigInt> &pubKeyParams, QList<Botan::BigInt> &allKeyParams);
+ void createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
+ QList<Botan::BigInt> &pubKeyParams, QList<Botan::BigInt> &allKeyParams);
+
+ static const QByteArray PrivKeyFileStartLineRsa;
+ static const QByteArray PrivKeyFileStartLineDsa;
+ static const QByteArray PrivKeyFileEndLineRsa;
+ static const QByteArray PrivKeyFileEndLineDsa;
+
+ QByteArray m_authKeyAlgoName;
+ QByteArray m_authPubKeyBlob;
+ QByteArray m_cachedPrivKeyContents;
+ QScopedPointer<Botan::PK_Signing_Key> m_authKey;
+ mutable Botan::AutoSeeded_RNG m_rng;
+};
+
+class SshDecryptionFacility : public SshAbstractCryptoFacility
+{
+public:
+ void decrypt(QByteArray &data, quint32 offset, quint32 dataSize) const;
+
+private:
+ virtual QByteArray cryptAlgoName(const SshKeyExchange &kex) const;
+ virtual QByteArray hMacAlgoName(const SshKeyExchange &kex) const;
+ virtual Botan::BlockCipherMode *makeCipherMode(Botan::BlockCipher *cipher,
+ Botan::BlockCipherModePaddingMethod *paddingMethod,
+ const Botan::InitializationVector &iv, const Botan::SymmetricKey &key);
+ virtual char ivChar() const { return 'B'; }
+ virtual char keyChar() const { return 'D'; }
+ virtual char macChar() const { return 'F'; }
+};
+
+} // namespace Internal
+} // namespace Core
+
+#endif // SSHABSTRACTCRYPTOFACILITY_P_H
diff --git a/src/plugins/coreplugin/ssh/sshdelayedsignal.cpp b/src/plugins/coreplugin/ssh/sshdelayedsignal.cpp
new file mode 100644
index 0000000000..d35075bf77
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshdelayedsignal.cpp
@@ -0,0 +1,165 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshdelayedsignal_p.h"
+
+#include "sftpchannel_p.h"
+#include "sshremoteprocess_p.h"
+
+#include <QtCore/QTimer>
+
+namespace Core {
+namespace Internal {
+
+SshDelayedSignal::SshDelayedSignal(const QWeakPointer<QObject> &checkObject)
+ : m_checkObject(checkObject)
+{
+ QTimer::singleShot(0, this, SLOT(handleTimeout()));
+}
+
+void SshDelayedSignal::handleTimeout()
+{
+ if (!m_checkObject.isNull())
+ emitSignal();
+ deleteLater();
+}
+
+
+SftpDelayedSignal::SftpDelayedSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject)
+ : SshDelayedSignal(checkObject), m_privChannel(privChannel) {}
+
+
+SftpInitializationFailedSignal::SftpInitializationFailedSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, const QString &reason)
+ : SftpDelayedSignal(privChannel, checkObject), m_reason(reason) {}
+
+void SftpInitializationFailedSignal::emitSignal()
+{
+ m_privChannel->emitInitializationFailedSignal(m_reason);
+}
+
+
+SftpInitializedSignal::SftpInitializedSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject)
+ : SftpDelayedSignal(privChannel, checkObject) {}
+
+void SftpInitializedSignal::emitSignal()
+{
+ m_privChannel->emitInitialized();
+}
+
+
+SftpJobFinishedSignal::SftpJobFinishedSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, SftpJobId jobId,
+ const QString &error)
+ : SftpDelayedSignal(privChannel, checkObject), m_jobId(jobId), m_error(error)
+{
+}
+
+void SftpJobFinishedSignal::emitSignal()
+{
+ m_privChannel->emitJobFinished(m_jobId, m_error);
+}
+
+
+SftpDataAvailableSignal::SftpDataAvailableSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, SftpJobId jobId,
+ const QString &data)
+ : SftpDelayedSignal(privChannel, checkObject), m_jobId(jobId), m_data(data) {}
+
+void SftpDataAvailableSignal::emitSignal()
+{
+ m_privChannel->emitDataAvailable(m_jobId, m_data);
+}
+
+
+SftpClosedSignal::SftpClosedSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject)
+ : SftpDelayedSignal(privChannel, checkObject) {}
+
+void SftpClosedSignal::emitSignal()
+{
+ m_privChannel->emitClosed();
+}
+
+
+SshRemoteProcessDelayedSignal::SshRemoteProcessDelayedSignal(SshRemoteProcessPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject)
+ : SshDelayedSignal(checkObject), m_privChannel(privChannel) {}
+
+
+SshRemoteProcessStartedSignal::SshRemoteProcessStartedSignal(SshRemoteProcessPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject)
+ : SshRemoteProcessDelayedSignal(privChannel, checkObject) {}
+
+void SshRemoteProcessStartedSignal::emitSignal()
+{
+ m_privChannel->emitStartedSignal();
+}
+
+
+SshRemoteProcessOutputAvailableSignal::SshRemoteProcessOutputAvailableSignal(SshRemoteProcessPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, const QByteArray &output)
+ : SshRemoteProcessDelayedSignal(privChannel, checkObject), m_output(output)
+{
+}
+
+void SshRemoteProcessOutputAvailableSignal::emitSignal()
+{
+ m_privChannel->emitOutputAvailableSignal(m_output);
+}
+
+
+SshRemoteProcessErrorOutputAvailableSignal::SshRemoteProcessErrorOutputAvailableSignal(SshRemoteProcessPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, const QByteArray &output)
+ : SshRemoteProcessDelayedSignal(privChannel, checkObject), m_output(output)
+{
+}
+
+void SshRemoteProcessErrorOutputAvailableSignal::emitSignal()
+{
+ m_privChannel->emitErrorOutputAvailableSignal(m_output);
+}
+
+
+SshRemoteProcessClosedSignal::SshRemoteProcessClosedSignal(SshRemoteProcessPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, int exitStatus)
+ : SshRemoteProcessDelayedSignal(privChannel, checkObject),
+ m_exitStatus(exitStatus)
+{
+}
+
+void SshRemoteProcessClosedSignal::emitSignal()
+{
+ m_privChannel->emitClosedSignal(m_exitStatus);
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sshdelayedsignal_p.h b/src/plugins/coreplugin/ssh/sshdelayedsignal_p.h
new file mode 100644
index 0000000000..09163fb686
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshdelayedsignal_p.h
@@ -0,0 +1,190 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHDELAYEDSIGNAL_P_H
+#define SSHDELAYEDSIGNAL_P_H
+
+#include "sftpdefs.h"
+
+#include <QtCore/QObject>
+#include <QtCore/QString>
+#include <QtCore/QWeakPointer>
+
+namespace Core {
+namespace Internal {
+class SftpChannelPrivate;
+class SshRemoteProcessPrivate;
+
+class SshDelayedSignal : public QObject
+{
+ Q_OBJECT
+public:
+ SshDelayedSignal(const QWeakPointer<QObject> &checkObject);
+
+private:
+ Q_SLOT void handleTimeout();
+ virtual void emitSignal()=0;
+
+ const QWeakPointer<QObject> m_checkObject;
+};
+
+
+class SftpDelayedSignal : public SshDelayedSignal
+{
+public:
+ SftpDelayedSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject);
+
+protected:
+ SftpChannelPrivate * const m_privChannel;
+};
+
+class SftpInitializationFailedSignal : public SftpDelayedSignal
+{
+public:
+ SftpInitializationFailedSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, const QString &reason);
+
+private:
+ virtual void emitSignal();
+
+ const QString m_reason;
+};
+
+class SftpInitializedSignal : public SftpDelayedSignal
+{
+public:
+ SftpInitializedSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject);
+
+private:
+ virtual void emitSignal();
+};
+
+class SftpJobFinishedSignal : public SftpDelayedSignal
+{
+public:
+ SftpJobFinishedSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, SftpJobId jobId,
+ const QString &error);
+
+private:
+ virtual void emitSignal();
+
+ const SftpJobId m_jobId;
+ const QString m_error;
+};
+
+class SftpDataAvailableSignal : public SftpDelayedSignal
+{
+public:
+ SftpDataAvailableSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, SftpJobId jobId,
+ const QString &data);
+
+private:
+ virtual void emitSignal();
+
+ const SftpJobId m_jobId;
+ const QString m_data;
+};
+
+class SftpClosedSignal : public SftpDelayedSignal
+{
+public:
+ SftpClosedSignal(SftpChannelPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject);
+
+private:
+ virtual void emitSignal();
+};
+
+
+class SshRemoteProcessDelayedSignal : public SshDelayedSignal
+{
+public:
+ SshRemoteProcessDelayedSignal(SshRemoteProcessPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject);
+
+protected:
+ SshRemoteProcessPrivate * const m_privChannel;
+};
+
+class SshRemoteProcessStartedSignal : public SshRemoteProcessDelayedSignal
+{
+public:
+ SshRemoteProcessStartedSignal(SshRemoteProcessPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject);
+
+private:
+ virtual void emitSignal();
+};
+
+class SshRemoteProcessOutputAvailableSignal
+ : public SshRemoteProcessDelayedSignal
+{
+public:
+ SshRemoteProcessOutputAvailableSignal(SshRemoteProcessPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, const QByteArray &output);
+
+private:
+ virtual void emitSignal();
+
+ const QByteArray m_output;
+};
+
+class SshRemoteProcessErrorOutputAvailableSignal
+ : public SshRemoteProcessDelayedSignal
+{
+public:
+ SshRemoteProcessErrorOutputAvailableSignal(SshRemoteProcessPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, const QByteArray &output);
+
+private:
+ virtual void emitSignal();
+
+ const QByteArray m_output;
+};
+
+class SshRemoteProcessClosedSignal : public SshRemoteProcessDelayedSignal
+{
+public:
+ SshRemoteProcessClosedSignal(SshRemoteProcessPrivate *privChannel,
+ const QWeakPointer<QObject> &checkObject, int exitStatus);
+
+private:
+ virtual void emitSignal();
+
+ const int m_exitStatus;
+};
+
+} // namespace Internal
+} // namespace Core
+
+#endif // SSHDELAYEDSIGNAL_P_H
diff --git a/src/plugins/coreplugin/ssh/ssherrors.h b/src/plugins/coreplugin/ssh/ssherrors.h
new file mode 100644
index 0000000000..01587edfc5
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/ssherrors.h
@@ -0,0 +1,43 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHERRORS_P_H
+#define SSHERRORS_P_H
+
+namespace Core {
+
+enum SshError {
+ SshNoError, SshSocketError, SshTimeoutError, SshProtocolError,
+ SshHostKeyError, SshKeyFileError, SshAuthenticationError,
+ SshClosedByServerError, SshInternalError
+};
+
+} // namespace Core
+
+#endif // SSHERRORS_P_H
diff --git a/src/plugins/coreplugin/ssh/sshexception_p.h b/src/plugins/coreplugin/ssh/sshexception_p.h
new file mode 100644
index 0000000000..6812fabc49
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshexception_p.h
@@ -0,0 +1,89 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHEXCEPTION_P_H
+#define SSHEXCEPTION_P_H
+
+#include "ssherrors.h"
+
+#include <QtCore/QByteArray>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QString>
+
+namespace Core {
+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
+{
+ SshServerException(SshErrorCode error, const QByteArray &errorStringServer,
+ const QString &errorStringUser)
+ : error(error), errorStringServer(errorStringServer),
+ errorStringUser(errorStringUser) {}
+
+ const SshErrorCode error;
+ const QByteArray errorStringServer;
+ const QString errorStringUser;
+};
+
+struct SshClientException
+{
+ SshClientException(SshError error, const QString &errorString)
+ : error(error), errorString(errorString) {}
+
+ const SshError error;
+ const QString errorString;
+};
+
+} // namespace Internal
+} // namespace Core
+
+#endif // SSHEXCEPTION_P_H
diff --git a/src/plugins/coreplugin/ssh/sshincomingpacket.cpp b/src/plugins/coreplugin/ssh/sshincomingpacket.cpp
new file mode 100644
index 0000000000..fdc274bbbd
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshincomingpacket.cpp
@@ -0,0 +1,442 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshincomingpacket_p.h"
+
+#include "sshcapabilities_p.h"
+
+namespace Core {
+namespace Internal {
+
+const QByteArray SshIncomingPacket::ExitStatusType("exit-status");
+const QByteArray SshIncomingPacket::ExitSignalType("exit-signal");
+
+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)
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("%s: current data size = %d, new data size = %d",
+ Q_FUNC_INFO, m_data.size(), newData.size());
+#endif
+
+ 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);
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Took %d bytes from new data", bytesToTake);
+#endif
+ if (currentDataSize() < minSize)
+ return;
+ }
+
+ const int bytesToTake
+ = qMin<quint32>(length() + 4 + macLength() - currentDataSize(),
+ newData.size());
+ moveFirstBytes(m_data, newData, bytesToTake);
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Took %d bytes from new data", bytesToTake);
+#endif
+ if (isComplete()) {
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Message complete. Overall size: %u, payload size: %u",
+ m_data.size(), m_length - paddingLength() - 1);
+#endif
+ 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 (SshPacketParseException &) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ "Key exchange failed: Server sent invalid SSH_MSG_KEXINIT packet.");
+ }
+ return exchangeData;
+}
+
+SshKeyExchangeReply SshIncomingPacket::extractKeyExchangeReply(const QByteArray &pubKeyAlgo) const
+{
+ Q_ASSERT(isComplete());
+ Q_ASSERT(type() == SSH_MSG_KEXDH_REPLY);
+
+ try {
+ SshKeyExchangeReply replyData;
+ quint32 offset = TypeOffset + 1;
+ const quint32 k_sLength
+ = SshPacketParser::asUint32(m_data, &offset);
+ if (offset + k_sLength > currentDataSize())
+ throw SshPacketParseException();
+ replyData.k_s = m_data.mid(offset - 4, k_sLength + 4);
+ if (SshPacketParser::asString(m_data, &offset) != pubKeyAlgo)
+ throw SshPacketParseException();
+
+ // DSS: p and q, RSA: e and n
+ replyData.parameters << SshPacketParser::asBigInt(m_data, &offset);
+ replyData.parameters << SshPacketParser::asBigInt(m_data, &offset);
+
+ // g and y
+ if (pubKeyAlgo == SshCapabilities::PubKeyDss) {
+ replyData.parameters << SshPacketParser::asBigInt(m_data, &offset);
+ replyData.parameters << SshPacketParser::asBigInt(m_data, &offset);
+ }
+
+ replyData.f = SshPacketParser::asBigInt(m_data, &offset);
+ offset += 4;
+ if (SshPacketParser::asString(m_data, &offset) != pubKeyAlgo)
+ throw SshPacketParseException();
+ replyData.signatureBlob = SshPacketParser::asString(m_data, &offset);
+ return replyData;
+ } catch (SshPacketParseException &) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ "Key exchange failed: "
+ "Server sent invalid SSH_MSG_KEXDH_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 (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 (SshPacketParseException &) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Invalid SSH_MSG_USERAUTH_BANNER.");
+ }
+}
+
+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 (SshPacketParseException &) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Invalid SSH_MSG_USERAUTH_BANNER.");
+ }
+}
+
+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 = SshPacketParser::asString(m_data, &offset);
+ openFailure.language = SshPacketParser::asString(m_data, &offset);
+ } catch (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 (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 (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 (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 (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);
+ if (SshPacketParser::asBool(m_data, &offset))
+ throw SshPacketParseException();
+ exitStatus.exitStatus = SshPacketParser::asUint32(m_data, &offset);
+ } catch (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);
+ 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 (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 (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 (SshPacketParseException &) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Invalid SSH_MSG_CHANNEL_REQUEST packet.");
+ }
+}
+
+void SshIncomingPacket::calculateLength() const
+{
+ Q_ASSERT(currentDataSize() >= minPacketSize());
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("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);
+#endif
+ m_decrypter.decrypt(m_data, 0, cipherBlockSize());
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("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);
+ qDebug("message type = %d", m_data.at(TypeOffset));
+#endif
+ m_length = SshPacketParser::asUint32(m_data, static_cast<quint32>(0));
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("decrypted length is %u", m_length);
+#endif
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sshincomingpacket_p.h b/src/plugins/coreplugin/ssh/sshincomingpacket_p.h
new file mode 100644
index 0000000000..9b10c8f799
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshincomingpacket_p.h
@@ -0,0 +1,186 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHINCOMINGPACKET_P_H
+#define SSHINCOMINGPACKET_P_H
+
+#include "sshpacket_p.h"
+
+#include "sshcryptofacility_p.h"
+#include "sshpacketparser_p.h"
+
+#include <QtCore/QList>
+#include <QtCore/QString>
+
+namespace Core {
+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> parameters; // DSS: p, q, g, y. RSA: e, n.
+ Botan::BigInt f;
+ QByteArray signatureBlob;
+};
+
+struct SshDisconnect
+{
+ quint32 reasonCode;
+ QString description;
+ QByteArray language;
+};
+
+struct SshUserAuthBanner
+{
+ QString message;
+ QByteArray language;
+};
+
+struct SshDebug
+{
+ bool display;
+ QString message;
+ QByteArray language;
+};
+
+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 &pubKeyAlgo) const;
+ SshDisconnect extractDisconnect() const;
+ SshUserAuthBanner extractUserAuthBanner() const;
+ SshDebug extractDebug() const;
+
+ 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;
+
+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 Core
+
+#endif // SSHINCOMINGPACKET_P_H
diff --git a/src/plugins/coreplugin/ssh/sshkeyexchange.cpp b/src/plugins/coreplugin/ssh/sshkeyexchange.cpp
new file mode 100644
index 0000000000..7875d2ecd0
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshkeyexchange.cpp
@@ -0,0 +1,197 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshkeyexchange_p.h"
+
+#include "sshbotanconversions_p.h"
+#include "sshcapabilities_p.h"
+#include "sshsendfacility_p.h"
+#include "sshexception_p.h"
+#include "sshincomingpacket_p.h"
+
+#include <botan/botan.h>
+#include <botan/dsa.h>
+#include <botan/look_pk.h>
+#include <botan/pubkey.h>
+#include <botan/rsa.h>
+
+#include <string>
+
+using namespace Botan;
+
+namespace Core {
+namespace Internal {
+
+namespace {
+
+ // For debugging
+ void printNameList(const char *listName, const SshNameList &list)
+ {
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("%s:", listName);
+ foreach (const QByteArray &name, list.names)
+ qDebug("%s", name.constData());
+#else
+ Q_UNUSED(listName);
+ Q_UNUSED(list);
+#endif
+ }
+} // anonymous namespace
+
+SshKeyExchange::SshKeyExchange(SshSendFacility &sendFacility)
+ : m_sendFacility(sendFacility)
+{
+}
+
+SshKeyExchange::~SshKeyExchange() {}
+
+void SshKeyExchange::sendKexInitPacket(const QByteArray &serverId)
+{
+ m_serverId = serverId;
+ const AbstractSshPacket::Payload &payload
+ = m_sendFacility.sendKeyExchangeInitPacket();
+ m_clientKexInitPayload = QByteArray(payload.data, payload.size);
+}
+
+bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit)
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("server requests key exchange");
+#endif
+ 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);
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("First packet follows: %d", kexInitParams.firstKexPacketFollows);
+#endif
+
+ const QByteArray &keyAlgo
+ = SshCapabilities::findBestMatch(SshCapabilities::KeyExchangeMethods,
+ kexInitParams.keyAlgorithms.names);
+ m_serverHostKeyAlgo
+ = SshCapabilities::findBestMatch(SshCapabilities::PublicKeyAlgorithms,
+ kexInitParams.serverHostKeyAlgorithms.names);
+ m_encryptionAlgo
+ = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms,
+ kexInitParams.encryptionAlgorithmsClientToServer.names);
+ m_decryptionAlgo
+ = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms,
+ kexInitParams.encryptionAlgorithmsServerToClient.names);
+ m_c2sHMacAlgo
+ = SshCapabilities::findBestMatch(SshCapabilities::MacAlgorithms,
+ kexInitParams.macAlgorithmsClientToServer.names);
+ m_s2cHMacAlgo
+ = SshCapabilities::findBestMatch(SshCapabilities::MacAlgorithms,
+ kexInitParams.macAlgorithmsServerToClient.names);
+ SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms,
+ kexInitParams.compressionAlgorithmsClientToServer.names);
+ SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms,
+ kexInitParams.compressionAlgorithmsServerToClient.names);
+
+ AutoSeeded_RNG rng;
+ m_dhKey.reset(new DH_PrivateKey(rng,
+ DL_Group(botanKeyExchangeAlgoName(keyAlgo))));
+
+ const AbstractSshPacket::Payload &payload = serverKexInit.payLoad();
+ m_serverKexInitPayload = QByteArray(payload.data, payload.size);
+ m_sendFacility.sendKeyDhInitPacket(m_dhKey->get_y());
+ return kexInitParams.firstKexPacketFollows;
+}
+
+void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply,
+ const QByteArray &clientId)
+{
+ const SshKeyExchangeReply &reply
+ = dhReply.extractKeyExchangeReply(m_serverHostKeyAlgo);
+ if (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;
+ concatenatedData += AbstractSshPacket::encodeMpInt(m_dhKey->get_y());
+ concatenatedData += AbstractSshPacket::encodeMpInt(reply.f);
+ SymmetricKey k = m_dhKey->derive_key(reply.f);
+ m_k = AbstractSshPacket::encodeMpInt(BigInt(k.begin(), k.length()));
+ concatenatedData += m_k;
+
+ m_hash.reset(get_hash(botanSha1Name()));
+ const SecureVector<byte> &hashResult
+ = m_hash->process(convertByteArray(concatenatedData),
+ concatenatedData.size());
+ m_h = convertByteArray(hashResult);
+
+ QScopedPointer<Public_Key> sigKey;
+ QScopedPointer<PK_Verifier> verifier;
+ if (m_serverHostKeyAlgo == SshCapabilities::PubKeyDss) {
+ const DL_Group group(reply.parameters.at(0), reply.parameters.at(1),
+ reply.parameters.at(2));
+ DSA_PublicKey * const dsaKey
+ = new DSA_PublicKey(group, reply.parameters.at(3));
+ sigKey.reset(dsaKey);
+ verifier.reset(get_pk_verifier(*dsaKey,
+ botanEmsaAlgoName(SshCapabilities::PubKeyDss)));
+ } else if (m_serverHostKeyAlgo == SshCapabilities::PubKeyRsa) {
+ RSA_PublicKey * const rsaKey
+ = new RSA_PublicKey(reply.parameters.at(1), reply.parameters.at(0));
+ sigKey.reset(rsaKey);
+ verifier.reset(get_pk_verifier(*rsaKey,
+ botanEmsaAlgoName(SshCapabilities::PubKeyRsa)));
+ } else {
+ Q_ASSERT(!"Impossible: Neither DSS nor RSA!");
+ }
+ const byte * const botanH = convertByteArray(m_h);
+ const Botan::byte * const botanSig
+ = convertByteArray(reply.signatureBlob);
+ if (!verifier->verify_message(botanH, m_h.size(), botanSig,
+ reply.signatureBlob.size())) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ "Invalid signature in SSH_MSG_KEXDH_REPLY packet.");
+ }
+
+ m_sendFacility.sendNewKeysPacket();
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sshkeyexchange_p.h b/src/plugins/coreplugin/ssh/sshkeyexchange_p.h
new file mode 100644
index 0000000000..076f5bedd6
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshkeyexchange_p.h
@@ -0,0 +1,87 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHKEYEXCHANGE_P_H
+#define SSHKEYEXCHANGE_P_H
+
+#include <botan/dh.h>
+
+#include <QtCore/QByteArray>
+#include <QtCore/QScopedPointer>
+
+namespace Botan { class HashFunction; }
+
+namespace Core {
+namespace Internal {
+
+class SshSendFacility;
+class SshIncomingPacket;
+
+class SshKeyExchange
+{
+public:
+ SshKeyExchange(SshSendFacility &sendFacility);
+ ~SshKeyExchange();
+
+ 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.data(); }
+ 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 m_serverId;
+ QByteArray m_clientKexInitPayload;
+ QByteArray m_serverKexInitPayload;
+ QScopedPointer<Botan::DH_PrivateKey> m_dhKey;
+ QByteArray m_k;
+ QByteArray m_h;
+ QByteArray m_serverHostKeyAlgo;
+ QByteArray m_encryptionAlgo;
+ QByteArray m_decryptionAlgo;
+ QByteArray m_c2sHMacAlgo;
+ QByteArray m_s2cHMacAlgo;
+ QScopedPointer<Botan::HashFunction> m_hash;
+ SshSendFacility &m_sendFacility;
+};
+
+} // namespace Internal
+} // namespace Core
+
+#endif // SSHKEYEXCHANGE_P_H
diff --git a/src/plugins/coreplugin/ssh/sshkeygenerator.cpp b/src/plugins/coreplugin/ssh/sshkeygenerator.cpp
index 17a63886e3..976d0094c7 100644
--- a/src/plugins/coreplugin/ssh/sshkeygenerator.cpp
+++ b/src/plugins/coreplugin/ssh/sshkeygenerator.cpp
@@ -1,55 +1,138 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
#include "sshkeygenerator.h"
-#include "ne7sshobject.h"
+#include "sshbotanconversions_p.h"
+#include "sshcapabilities_p.h"
+#include "sshpacket_p.h"
-#include <QtCore/QFile>
-#include <QtCore/QTemporaryFile>
+#include <botan/auto_rng.h>
+#include <botan/bigint.h>
+#include <botan/der_enc.h>
+#include <botan/dsa.h>
+#include <botan/pem.h>
+#include <botan/pkcs8.h>
+#include <botan/rsa.h>
+#include <botan/x509_key.h>
-#include <ne7ssh.h>
+#include <QtCore/QDateTime>
namespace Core {
-SshKeyGenerator::SshKeyGenerator()
-{
-}
+using namespace Botan;
+using namespace Internal;
-bool SshKeyGenerator::generateKeys(KeyType type, const QString &id, int keySize)
+SshKeyGenerator::SshKeyGenerator() { }
+
+bool SshKeyGenerator::generateKeys(KeyType type, PrivateKeyFormat format,
+ int keySize)
{
- QTemporaryFile tmpPubKeyFile;
- QTemporaryFile tmpPrivKeyFile;
- if (!tmpPubKeyFile.open() || !tmpPrivKeyFile.open()) {
- m_error = tr("Error creating temporary files.");
- return false;
- }
- tmpPubKeyFile.setAutoRemove(false);
- tmpPubKeyFile.close();
- tmpPrivKeyFile.close();
- const char * const typeStr = type == Rsa ? "rsa" : "dsa";
- Internal::Ne7SshObject::Ptr ne7Object
- = Internal::Ne7SshObject::instance()->get();
- if (!ne7Object->generateKeyPair(typeStr, id.toUtf8(),
- tmpPrivKeyFile.fileName().toUtf8(),
- tmpPubKeyFile.fileName().toUtf8(), keySize)) {
- // TODO: Race condition on pop() call. Perhaps not use Net7 errors? Or hack API
- m_error = tr("Error generating keys: %1")
- .arg(ne7Object->errors()->pop());
+ try {
+ AutoSeeded_RNG rng;
+ KeyPtr key;
+ if (type == Rsa)
+ key = KeyPtr(new RSA_PrivateKey(rng, keySize));
+ else
+ key = KeyPtr(new DSA_PrivateKey(rng, DL_Group(rng, DL_Group::Strong,
+ keySize)));
+ return format == Pkcs8
+ ? generatePkcs8Keys(key) : generateOpenSslKeys(key, type);
+ } catch (Botan::Exception &e) {
+ m_error = tr("Error generating key: %1").arg(e.what());
return false;
}
+}
- if (!tmpPubKeyFile.open() || !tmpPrivKeyFile.open()) {
- m_error = tr("Error reading temporary files.");
- return false;
+bool SshKeyGenerator::generatePkcs8Keys(const KeyPtr &key)
+{
+ generatePkcs8Key(key, false);
+ generatePkcs8Key(key, true);
+ return true;
+}
+
+void SshKeyGenerator::generatePkcs8Key(const KeyPtr &key, bool privateKey)
+{
+ Pipe pipe;
+ pipe.start_msg();
+ QByteArray *keyData;
+ if (privateKey) {
+ PKCS8::encode(*key, pipe);
+ keyData = &m_privateKey;
+ } else {
+ X509::encode(*key, pipe);
+ keyData = &m_publicKey;
}
+ pipe.end_msg();
+ keyData->resize(pipe.remaining(pipe.message_count() - 1));
+ pipe.read(convertByteArray(*keyData), keyData->size(),
+ pipe.message_count() - 1);
+}
- m_publicKey = tmpPubKeyFile.readAll();
- m_privateKey = tmpPrivKeyFile.readAll();
- if (tmpPubKeyFile.error() != QFile::NoError
- || tmpPrivKeyFile.error() != QFile::NoError) {
- m_error = tr("Error reading temporary files.");
- return false;
+bool SshKeyGenerator::generateOpenSslKeys(const KeyPtr &key, KeyType type)
+{
+ QList<BigInt> publicParams;
+ QList<BigInt> allParams;
+ QByteArray keyId;
+ if (type == Rsa) {
+ const QSharedPointer<RSA_PrivateKey> rsaKey
+ = key.dynamicCast<RSA_PrivateKey>();
+ publicParams << rsaKey->get_e() << rsaKey->get_n();
+ allParams << rsaKey->get_n() << rsaKey->get_e() << rsaKey->get_d()
+ << rsaKey->get_p() << rsaKey->get_q();
+ keyId = SshCapabilities::PubKeyRsa;
+ } else {
+ const QSharedPointer<DSA_PrivateKey> dsaKey
+ = key.dynamicCast<DSA_PrivateKey>();
+ publicParams << dsaKey->group_p() << dsaKey->group_q()
+ << dsaKey->group_g() << dsaKey->get_y();
+ allParams << publicParams << dsaKey->get_x();
+ keyId = SshCapabilities::PubKeyDss;
}
- m_type = type;
+ QByteArray publicKeyBlob = AbstractSshPacket::encodeString(keyId);
+ foreach (const BigInt &b, publicParams)
+ publicKeyBlob += AbstractSshPacket::encodeMpInt(b);
+ publicKeyBlob = publicKeyBlob.toBase64();
+ const QByteArray id = "QtCreator/"
+ + QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8();
+ m_publicKey = keyId + ' ' + publicKeyBlob + ' ' + id;
+
+ DER_Encoder encoder;
+ encoder.start_cons(SEQUENCE).encode (0U);
+ foreach (const BigInt &b, allParams)
+ encoder.encode(b);
+ encoder.end_cons();
+ const char * const label
+ = type == Rsa ? "RSA PRIVATE KEY" : "DSA PRIVATE KEY";
+ m_privateKey
+ = QByteArray(PEM_Code::encode (encoder.get_contents(), label).c_str());
return true;
}
diff --git a/src/plugins/coreplugin/ssh/sshkeygenerator.h b/src/plugins/coreplugin/ssh/sshkeygenerator.h
index a68237e576..ada06150a0 100644
--- a/src/plugins/coreplugin/ssh/sshkeygenerator.h
+++ b/src/plugins/coreplugin/ssh/sshkeygenerator.h
@@ -1,10 +1,43 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
#ifndef SSHKEYGENERATOR_H
#define SSHKEYGENERATOR_H
#include <coreplugin/core_global.h>
#include <QtCore/QCoreApplication>
-#include <QtCore/QPair>
+#include <QtCore/QSharedPointer>
+
+namespace Botan {
+ class Private_Key;
+}
namespace Core {
@@ -13,19 +46,28 @@ class CORE_EXPORT SshKeyGenerator
Q_DECLARE_TR_FUNCTIONS(SshKeyGenerator)
public:
enum KeyType { Rsa, Dsa };
+ enum PrivateKeyFormat { Pkcs8, OpenSsl };
SshKeyGenerator();
- bool generateKeys(KeyType type, const QString &id, int keySize);
+ bool generateKeys(KeyType type, PrivateKeyFormat format, int keySize);
QString error() const { return m_error; }
- QString privateKey() const { return m_privateKey; }
- QString publicKey() const { return m_publicKey; }
+ QByteArray privateKey() const { return m_privateKey; }
+ QByteArray publicKey() const { return m_publicKey; }
KeyType type() const { return m_type; }
+ PrivateKeyFormat format() const { return m_format; }
private:
+ typedef QSharedPointer<Botan::Private_Key> KeyPtr;
+
+ bool generatePkcs8Keys(const KeyPtr &key);
+ void generatePkcs8Key(const KeyPtr &key, bool privateKey);
+ bool generateOpenSslKeys(const KeyPtr &key, KeyType type);
+
QString m_error;
- QString m_publicKey;
- QString m_privateKey;
+ QByteArray m_publicKey;
+ QByteArray m_privateKey;
KeyType m_type;
+ PrivateKeyFormat m_format;
};
} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sshoutgoingpacket.cpp b/src/plugins/coreplugin/ssh/sshoutgoingpacket.cpp
new file mode 100644
index 0000000000..c6cf99443d
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshoutgoingpacket.cpp
@@ -0,0 +1,284 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshoutgoingpacket_p.h"
+
+#include "sshcapabilities_p.h"
+#include "sshcryptofacility_p.h"
+
+#include <QtCore/QtEndian>
+
+namespace Core {
+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();
+}
+
+void 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.
+ finalize();
+}
+
+void SshOutgoingPacket::generateKeyDhInitPacket(const Botan::BigInt &e)
+{
+ init(SSH_MSG_KEXDH_INIT).appendMpInt(e).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::generateUserAuthByPwdRequestPacket(const QByteArray &user,
+ const QByteArray &service, const QByteArray &pwd)
+{
+ init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service)
+ .appendString("password").appendBool(false).appendString(pwd)
+ .finalize();
+}
+
+void SshOutgoingPacket::generateUserAuthByKeyRequestPacket(const QByteArray &user,
+ const QByteArray &service)
+{
+ init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service)
+ .appendString("publickey").appendBool(true)
+ .appendString(m_encrypter.authenticationAlgorithmName())
+ .appendString(m_encrypter.authenticationPublicKey());
+ const QByteArray &dataToSign = m_data.mid(PayloadOffset);
+ appendString(m_encrypter.authenticationKeySignature(dataToSign));
+ finalize();
+}
+
+void SshOutgoingPacket::generateRequestFailurePacket()
+{
+ init(SSH_MSG_REQUEST_FAILURE).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::generateEnvPacket(quint32 remoteChannel,
+ const QByteArray &var, const QByteArray &value)
+{
+ init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("env")
+ .appendBool(false).appendString(var).appendString(value);
+}
+
+void SshOutgoingPacket::generateExecPacket(quint32 remoteChannel,
+ const QByteArray &command)
+{
+ init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("exec")
+ .appendBool(true).appendString(command).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::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;
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Encrypting packet of type %u", m_data.at(TypeOffset));
+#endif
+ encrypt();
+ 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 Core
diff --git a/src/plugins/coreplugin/ssh/sshoutgoingpacket_p.h b/src/plugins/coreplugin/ssh/sshoutgoingpacket_p.h
new file mode 100644
index 0000000000..eb9c2f520d
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshoutgoingpacket_p.h
@@ -0,0 +1,98 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHOUTGOINGPACKET_P_H
+#define SSHOUTGOINGPACKET_P_H
+
+#include "sshpacket_p.h"
+
+namespace Core {
+namespace Internal {
+
+class SshEncryptionFacility;
+
+class SshOutgoingPacket : public AbstractSshPacket
+{
+public:
+ SshOutgoingPacket(const SshEncryptionFacility &encrypter,
+ const quint32 &seqNr);
+
+ void generateKeyExchangeInitPacket();
+ void generateKeyDhInitPacket(const Botan::BigInt &e);
+ void generateNewKeysPacket();
+ void generateDisconnectPacket(SshErrorCode reason,
+ const QByteArray &reasonString);
+ void generateMsgUnimplementedPacket(quint32 serverSeqNr);
+ void generateUserAuthServiceRequestPacket();
+ void generateUserAuthByPwdRequestPacket(const QByteArray &user,
+ const QByteArray &service, const QByteArray &pwd);
+ void generateUserAuthByKeyRequestPacket(const QByteArray &user,
+ const QByteArray &service);
+ void generateRequestFailurePacket();
+ void generateSessionPacket(quint32 channelId, quint32 windowSize,
+ quint32 maxPacketSize);
+ void generateEnvPacket(quint32 remoteChannel, const QByteArray &var,
+ const QByteArray &value);
+ void generateExecPacket(quint32 remoteChannel, const QByteArray &command);
+ 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);
+
+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 Core
+
+#endif // SSHOUTGOINGPACKET_P_H
diff --git a/src/plugins/coreplugin/ssh/sshpacket.cpp b/src/plugins/coreplugin/ssh/sshpacket.cpp
new file mode 100644
index 0000000000..ff70509355
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshpacket.cpp
@@ -0,0 +1,167 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshpacket_p.h"
+
+#include "sshcapabilities_p.h"
+#include "sshcryptofacility_p.h"
+#include "sshexception_p.h"
+#include "sshpacketparser_p.h"
+
+#include <QtCore/QDebug>
+
+#include <cctype>
+
+namespace Core {
+namespace Internal {
+
+const quint32 AbstractSshPacket::PaddingLengthOffset = 4;
+const quint32 AbstractSshPacket::PayloadOffset = PaddingLengthOffset + 1;
+const quint32 AbstractSshPacket::TypeOffset = PayloadOffset;
+const quint32 AbstractSshPacket::MinPaddingLength = 4;
+
+namespace {
+
+ void printByteArray(const QByteArray &data)
+ {
+#ifdef CREATOR_SSH_DEBUG
+ for (int i = 0; i < data.count(); ++i)
+ qDebug() << std::hex << (static_cast<unsigned int>(data[i]) & 0xff) << " ";
+#else
+ Q_UNUSED(data);
+#endif
+ }
+} // anonymous namespace
+
+
+AbstractSshPacket::AbstractSshPacket() : m_length(0) { }
+AbstractSshPacket::~AbstractSshPacket() {}
+
+bool AbstractSshPacket::isComplete() const
+{
+ if (currentDataSize() < minPacketSize())
+ return false;
+ Q_ASSERT(4 + length() + macLength() >= currentDataSize());
+ 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));
+}
+
+AbstractSshPacket::Payload AbstractSshPacket::payLoad() const
+{
+ Payload p;
+ p.data = m_data.constData() + PayloadOffset;
+ p.size = length() - paddingLength() - 1;
+ return p;
+}
+
+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 = 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), 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)
+{
+ const quint32 length = qToBigEndian(data.size() - 4);
+ data.replace(0, 4, reinterpret_cast<const char *>(&length), 4);
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sshpacket_p.h b/src/plugins/coreplugin/ssh/sshpacket_p.h
new file mode 100644
index 0000000000..7120f001fd
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshpacket_p.h
@@ -0,0 +1,137 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHPACKET_P_H
+#define SSHPACKET_P_H
+
+#include "sshexception_p.h"
+
+#include <QtCore/QtEndian>
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+
+#include <botan/bigint.h>
+
+namespace Core {
+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_KEXDH_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_GLOBAL_REQUEST = 80,
+ SSH_MSG_REQUEST_SUCCESS = 81,
+ SSH_MSG_REQUEST_FAILURE = 82,
+
+ 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,
+};
+
+enum SshExtendedDataType { SSH_EXTENDED_DATA_STDERR = 1 };
+
+class SshAbstractCryptoFacility;
+
+class 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; }
+
+ struct Payload { const char *data; quint32 size; };
+ Payload 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 Core
+
+#endif // SSHPACKET_P_H
diff --git a/src/plugins/coreplugin/ssh/sshpacketparser.cpp b/src/plugins/coreplugin/ssh/sshpacketparser.cpp
new file mode 100644
index 0000000000..2f339c03d9
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshpacketparser.cpp
@@ -0,0 +1,153 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshpacketparser_p.h"
+
+#include <cctype>
+
+namespace Core {
+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();
+ const quint32 value = ((data.at(offset) & 0xff) << 24)
+ + ((data.at(offset + 1) & 0xff) << 16)
+ + ((data.at(offset + 2) & 0xff) << 8) + (data.at(offset + 3) & 0xff);
+ return value;
+}
+
+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();
+ const quint64 value = (static_cast<quint64>(data.at(offset) & 0xff) << 56)
+ + (static_cast<quint64>(data.at(offset + 1) & 0xff) << 48)
+ + (static_cast<quint64>(data.at(offset + 2) & 0xff) << 40)
+ + (static_cast<quint64>(data.at(offset + 3) & 0xff) << 32)
+ + ((data.at(offset + 4) & 0xff) << 24)
+ + ((data.at(offset + 5) & 0xff) << 16)
+ + ((data.at(offset + 6) & 0xff) << 8)
+ + (data.at(offset + 7) & 0xff);
+ return value;
+}
+
+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)
+{
+ 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 Core
diff --git a/src/plugins/coreplugin/ssh/sshpacketparser_p.h b/src/plugins/coreplugin/ssh/sshpacketparser_p.h
new file mode 100644
index 0000000000..253f256b7b
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshpacketparser_p.h
@@ -0,0 +1,81 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHPACKETPARSER_P_H
+#define SSHPACKETPARSER_P_H
+
+#include <botan/bigint.h>
+
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+#include <QtCore/QString>
+
+namespace Core {
+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 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 Core
+
+#endif // SSHPACKETPARSER_P_H
diff --git a/src/plugins/coreplugin/ssh/sshremoteprocess.cpp b/src/plugins/coreplugin/ssh/sshremoteprocess.cpp
new file mode 100644
index 0000000000..c9566e9aca
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshremoteprocess.cpp
@@ -0,0 +1,270 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshremoteprocess.h"
+#include "sshremoteprocess_p.h"
+
+#include "sshdelayedsignal_p.h"
+#include "sshincomingpacket_p.h"
+#include "sshsendfacility_p.h"
+
+#include <botan/exceptn.h>
+
+namespace Core {
+
+const QByteArray SshRemoteProcess::AbrtSignal("ABRT");
+const QByteArray SshRemoteProcess::AlrmSignal("ALRM");
+const QByteArray SshRemoteProcess::FpeSignal("FPE");
+const QByteArray SshRemoteProcess::HupSignal("HUP");
+const QByteArray SshRemoteProcess::IllSignal("ILL");
+const QByteArray SshRemoteProcess::IntSignal("INT");
+const QByteArray SshRemoteProcess::KillSignal("KILL");
+const QByteArray SshRemoteProcess::PipeSignal("PIPE");
+const QByteArray SshRemoteProcess::QuitSignal("QUIT");
+const QByteArray SshRemoteProcess::SegvSignal("SEGV");
+const QByteArray SshRemoteProcess::TermSignal("TERM");
+const QByteArray SshRemoteProcess::Usr1Signal("USR1");
+const QByteArray SshRemoteProcess::Usr2Signal("USR2");
+
+SshRemoteProcess::SshRemoteProcess(const QByteArray &command, quint32 channelId,
+ Internal::SshSendFacility &sendFacility)
+ : d(new Internal::SshRemoteProcessPrivate(command, channelId, sendFacility, this))
+{
+}
+
+SshRemoteProcess::~SshRemoteProcess()
+{
+ Q_ASSERT(d->channelState() == Internal::SshRemoteProcessPrivate::Inactive
+ || d->channelState() == Internal::SshRemoteProcessPrivate::CloseRequested
+ || d->channelState() == Internal::SshRemoteProcessPrivate::Closed);
+ delete d;
+}
+
+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::start()
+{
+ if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive) {
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("process start requested, channel id = %u", d->localChannelId());
+#endif
+ d->requestSessionStart();
+ }
+}
+
+void SshRemoteProcess::sendSignal(const QByteArray &signal)
+{
+ try {
+ if (isRunning())
+ d->m_sendFacility.sendChannelSignalPacket(d->remoteChannel(),
+ signal);
+ } catch (Botan::Exception &e) {
+ d->setError(QString::fromAscii(e.what()));
+ d->closeChannel();
+ }
+}
+
+void SshRemoteProcess::closeChannel()
+{
+ d->closeChannel();
+}
+
+void SshRemoteProcess::sendInput(const QByteArray &data)
+{
+ if (isRunning())
+ d->sendData(data);
+}
+
+bool SshRemoteProcess::isRunning() const
+{
+ return d->m_procState == Internal::SshRemoteProcessPrivate::Running;
+}
+
+QString SshRemoteProcess::errorString() const { return d->errorString(); }
+
+int SshRemoteProcess::exitCode() const { return d->m_exitCode; }
+
+QByteArray SshRemoteProcess::exitSignal() const { return d->m_signal; }
+
+namespace Internal {
+
+SshRemoteProcessPrivate::SshRemoteProcessPrivate(const QByteArray &command,
+ quint32 channelId, SshSendFacility &sendFacility, SshRemoteProcess *proc)
+ : AbstractSshChannel(channelId, sendFacility), m_procState(NotYetStarted),
+ m_wasRunning(false), m_exitCode(0), m_command(command), m_proc(proc)
+{
+}
+
+void SshRemoteProcessPrivate::setProcState(ProcessState newState)
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("channel: old state = %d,new state = %d", m_procState, newState);
+#endif
+ m_procState = newState;
+ if (newState == StartFailed) {
+ createClosedSignal(SshRemoteProcess::FailedToStart);
+ } else if (newState == Running) {
+ m_wasRunning = true;
+ createStartedSignal();
+ }
+}
+
+void SshRemoteProcessPrivate::closeHook()
+{
+ if (m_wasRunning) {
+ if (!m_signal.isEmpty())
+ createClosedSignal(SshRemoteProcess::KilledBySignal);
+ else
+ createClosedSignal(SshRemoteProcess::ExitedNormally);
+ }
+}
+
+void SshRemoteProcessPrivate::handleOpenSuccessInternal()
+{
+ foreach (const EnvVar &envVar, m_env) {
+ m_sendFacility.sendEnvPacket(remoteChannel(), envVar.first,
+ envVar.second);
+ }
+
+ m_sendFacility.sendExecPacket(remoteChannel(), m_command);
+ setProcState(ExecRequested);
+}
+
+void SshRemoteProcessPrivate::handleOpenFailureInternal()
+{
+ setProcState(StartFailed);
+}
+
+void SshRemoteProcessPrivate::handleChannelSuccess()
+{
+ if (m_procState != ExecRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_MSG_CHANNEL_SUCCESS message.");
+ }
+ setProcState(Running);
+}
+
+void SshRemoteProcessPrivate::handleChannelFailure()
+{
+ if (m_procState != ExecRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_MSG_CHANNEL_FAILURE message.");
+ }
+
+ setProcState(StartFailed);
+ closeChannel();
+}
+
+void SshRemoteProcessPrivate::handleChannelDataInternal(const QByteArray &data)
+{
+ createOutputAvailableSignal(data);
+}
+
+void SshRemoteProcessPrivate::handleChannelExtendedDataInternal(quint32 type,
+ const QByteArray &data)
+{
+ if (type != SSH_EXTENDED_DATA_STDERR)
+ qWarning("Unknown extended data type %u", type);
+ else
+ createErrorOutputAvailableSignal(data);
+}
+
+void SshRemoteProcessPrivate::handleChannelRequest(const SshIncomingPacket &packet)
+{
+ checkChannelActive();
+ const QByteArray &requestType = packet.extractChannelRequestType();
+ if (requestType == SshIncomingPacket::ExitStatusType) {
+ const SshChannelExitStatus status = packet.extractChannelExitStatus();
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Process exiting with exit code %d", status.exitStatus);
+#endif
+ m_exitCode = status.exitStatus;
+ m_procState = Exited;
+ } else if (requestType == SshIncomingPacket::ExitSignalType) {
+ const SshChannelExitSignal &signal = packet.extractChannelExitSignal();
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Exit due to signal %s", signal.signal.data());
+#endif
+ setError(signal.error);
+ m_signal = signal.signal;
+ m_procState = Exited;
+ } else {
+ qWarning("Ignoring unknown request type '%s'", requestType.data());
+ }
+}
+
+void SshRemoteProcessPrivate::createStartedSignal()
+{
+ new SshRemoteProcessStartedSignal(this, QWeakPointer<SshRemoteProcess>(m_proc));
+}
+
+void SshRemoteProcessPrivate::emitStartedSignal()
+{
+ emit m_proc->started();
+}
+
+void SshRemoteProcessPrivate::createOutputAvailableSignal(const QByteArray &output)
+{
+ new SshRemoteProcessOutputAvailableSignal(this,
+ QWeakPointer<SshRemoteProcess>(m_proc), output);
+}
+
+void SshRemoteProcessPrivate::emitOutputAvailableSignal(const QByteArray &output)
+{
+ emit m_proc->outputAvailable(output);
+}
+
+void SshRemoteProcessPrivate::createErrorOutputAvailableSignal(const QByteArray &output)
+{
+ new SshRemoteProcessErrorOutputAvailableSignal(this,
+ QWeakPointer<SshRemoteProcess>(m_proc), output);
+}
+
+void SshRemoteProcessPrivate::emitErrorOutputAvailableSignal(const QByteArray &output)
+{
+ emit m_proc->errorOutputAvailable(output);
+}
+
+void SshRemoteProcessPrivate::createClosedSignal(int exitStatus)
+{
+ new SshRemoteProcessClosedSignal(this,
+ QWeakPointer<SshRemoteProcess>(m_proc), exitStatus);
+}
+
+void SshRemoteProcessPrivate::emitClosedSignal(int exitStatus)
+{
+ emit m_proc->closed(exitStatus);
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sshremoteprocess.h b/src/plugins/coreplugin/ssh/sshremoteprocess.h
new file mode 100644
index 0000000000..941894990f
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshremoteprocess.h
@@ -0,0 +1,130 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHREMOTECOMMAND_H
+#define SSHREMOTECOMMAND_H
+
+#include <coreplugin/core_global.h>
+
+#include <QtCore/QObject>
+#include <QtCore/QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QByteArray;
+QT_END_NAMESPACE
+
+namespace Core {
+namespace Internal {
+class SshChannelManager;
+class SshRemoteProcessPrivate;
+class SshSendFacility;
+} // namespace Internal
+
+
+/*
+ * 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.
+ * A closeChannel() function is provided, but rarely useful, because
+ * a) when the process ends, the channel is closed automatically, and
+ * b) closing a channel will not necessarily kill the remote process.
+ * Therefore, the only sensible use case for calling closeChannel() is to
+ * get rid of an SshRemoteProces object before the process is actually started.
+ * Note that the process does not have a terminal, so you can't use it
+ * for applications that require one.
+ */
+class CORE_EXPORT SshRemoteProcess : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(SshRemoteProcess)
+
+ friend class Internal::SshChannelManager;
+ friend class Internal::SshRemoteProcessPrivate;
+
+public:
+ typedef QSharedPointer<SshRemoteProcess> Ptr;
+ enum ExitStatus { FailedToStart, KilledBySignal, ExitedNormally };
+
+ static const QByteArray AbrtSignal;
+ static const QByteArray AlrmSignal;
+ static const QByteArray FpeSignal;
+ static const QByteArray HupSignal;
+ static const QByteArray IllSignal;
+ static const QByteArray IntSignal;
+ static const QByteArray KillSignal;
+ static const QByteArray PipeSignal;
+ static const QByteArray QuitSignal;
+ static const QByteArray SegvSignal;
+ static const QByteArray TermSignal;
+ static const QByteArray Usr1Signal;
+ static const QByteArray Usr2Signal;
+
+ ~SshRemoteProcess();
+
+ /*
+ * 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 start();
+ void closeChannel();
+
+ bool isRunning() const;
+ QString errorString() const;
+ int exitCode() const;
+ QByteArray exitSignal() const;
+
+ // Note: This is ignored by the OpenSSH server.
+ void sendSignal(const QByteArray &signal);
+ void kill() { sendSignal(KillSignal); }
+
+ void sendInput(const QByteArray &data); // Should usually have a trailing newline.
+
+signals:
+ void started();
+ void outputAvailable(const QByteArray &output);
+ void errorOutputAvailable(const QByteArray &output);
+
+ /*
+ * 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);
+
+ Internal::SshRemoteProcessPrivate *d;
+};
+
+} // namespace Core
+
+#endif // SSHREMOTECOMMAND_H
diff --git a/src/plugins/coreplugin/ssh/sshremoteprocess_p.h b/src/plugins/coreplugin/ssh/sshremoteprocess_p.h
new file mode 100644
index 0000000000..951ca24731
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshremoteprocess_p.h
@@ -0,0 +1,96 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHREMOTEPROCESS_P_H
+#define SSHREMOTEPROCESS_P_H
+
+#include "sshchannel_p.h"
+
+#include <QtCore/QList>
+#include <QtCore/QPair>
+
+namespace Core {
+class SshRemoteProcess;
+
+namespace Internal {
+class SshSendFacility;
+
+class SshRemoteProcessPrivate : public AbstractSshChannel
+{
+ friend class Core::SshRemoteProcess;
+public:
+ enum ProcessState {
+ NotYetStarted, ExecRequested, StartFailed,Running, Exited
+ };
+
+ virtual void handleChannelSuccess();
+ virtual void handleChannelFailure();
+
+ virtual void closeHook();
+
+ void emitStartedSignal();
+ void emitOutputAvailableSignal(const QByteArray &output);
+ void emitErrorOutputAvailableSignal(const QByteArray &output);
+ void emitClosedSignal(int exitStatus);
+
+private:
+ SshRemoteProcessPrivate(const QByteArray &command, quint32 channelId,
+ SshSendFacility &sendFacility, SshRemoteProcess *proc);
+
+ virtual void handleOpenSuccessInternal();
+ virtual void handleOpenFailureInternal();
+ virtual void handleChannelDataInternal(const QByteArray &data);
+ virtual void handleChannelExtendedDataInternal(quint32 type,
+ const QByteArray &data);
+ virtual void handleChannelRequest(const SshIncomingPacket &packet);
+
+ void setProcState(ProcessState newState);
+
+ void createStartedSignal();
+ void createOutputAvailableSignal(const QByteArray &output);
+ void createErrorOutputAvailableSignal(const QByteArray &output);
+ void createClosedSignal(int exitStatus);
+
+ ProcessState m_procState;
+ bool m_wasRunning;
+ QByteArray m_signal;
+ int m_exitCode;
+
+ const QByteArray m_command;
+
+ typedef QPair<QByteArray, QByteArray> EnvVar;
+ QList<EnvVar> m_env;
+
+ SshRemoteProcess *m_proc;
+};
+
+} // namespace Internal
+} // namespace Core
+
+#endif // SSHREMOTEPROCESS_P_H
diff --git a/src/plugins/coreplugin/ssh/sshsendfacility.cpp b/src/plugins/coreplugin/ssh/sshsendfacility.cpp
new file mode 100644
index 0000000000..3c793af24d
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshsendfacility.cpp
@@ -0,0 +1,191 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "sshsendfacility_p.h"
+
+#include "sshkeyexchange_p.h"
+#include "sshoutgoingpacket_p.h"
+
+#include <QtNetwork/QTcpSocket>
+
+namespace Core {
+namespace Internal {
+
+SshSendFacility::SshSendFacility(QTcpSocket *socket)
+ : m_clientSeqNr(0), m_socket(socket),
+ m_outgoingPacket(m_encrypter, m_clientSeqNr)
+{
+}
+
+void SshSendFacility::sendPacket()
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Sending packet, client seq nr is %u", m_clientSeqNr);
+#endif
+ 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);
+}
+
+SshOutgoingPacket::Payload SshSendFacility::sendKeyExchangeInitPacket()
+{
+ m_outgoingPacket.generateKeyExchangeInitPacket();
+ sendPacket();
+ return m_outgoingPacket.payLoad();
+}
+
+void SshSendFacility::sendKeyDhInitPacket(const Botan::BigInt &e)
+{
+ m_outgoingPacket.generateKeyDhInitPacket(e);
+ 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::sendUserAuthByPwdRequestPacket(const QByteArray &user,
+ const QByteArray &service, const QByteArray &pwd)
+{
+ m_outgoingPacket.generateUserAuthByPwdRequestPacket(user, service, pwd);
+ sendPacket();
+ }
+
+void SshSendFacility::sendUserAuthByKeyRequestPacket(const QByteArray &user,
+ const QByteArray &service)
+{
+ m_outgoingPacket.generateUserAuthByKeyRequestPacket(user, service);
+ sendPacket();
+}
+
+void SshSendFacility::sendRequestFailurePacket()
+{
+ m_outgoingPacket.generateRequestFailurePacket();
+ sendPacket();
+}
+
+void SshSendFacility::sendSessionPacket(quint32 channelId, quint32 windowSize,
+ quint32 maxPacketSize)
+{
+ m_outgoingPacket.generateSessionPacket(channelId, windowSize,
+ maxPacketSize);
+ sendPacket();
+}
+
+void SshSendFacility::sendEnvPacket(quint32 remoteChannel,
+ const QByteArray &var, const QByteArray &value)
+{
+ m_outgoingPacket.generateEnvPacket(remoteChannel, var, value);
+ sendPacket();
+}
+
+void SshSendFacility::sendExecPacket(quint32 remoteChannel,
+ const QByteArray &command)
+{
+ m_outgoingPacket.generateExecPacket(remoteChannel, command);
+ 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();
+}
+
+} // namespace Internal
+} // namespace Core
diff --git a/src/plugins/coreplugin/ssh/sshsendfacility_p.h b/src/plugins/coreplugin/ssh/sshsendfacility_p.h
new file mode 100644
index 0000000000..6f1cdf76f3
--- /dev/null
+++ b/src/plugins/coreplugin/ssh/sshsendfacility_p.h
@@ -0,0 +1,90 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef SSHCONNECTIONOUTSTATE_P_H
+#define SSHCONNECTIONOUTSTATE_P_H
+
+#include "sshcryptofacility_p.h"
+#include "sshoutgoingpacket_p.h"
+
+QT_BEGIN_NAMESPACE
+class QTcpSocket;
+QT_END_NAMESPACE
+
+
+namespace Core {
+namespace Internal {
+class SshKeyExchange;
+
+class SshSendFacility
+{
+public:
+ SshSendFacility(QTcpSocket *socket);
+ void reset();
+ void recreateKeys(const SshKeyExchange &keyExchange);
+ void createAuthenticationKey(const QByteArray &privKeyFileContents);
+
+ SshOutgoingPacket::Payload sendKeyExchangeInitPacket();
+ void sendKeyDhInitPacket(const Botan::BigInt &e);
+ void sendNewKeysPacket();
+ void sendDisconnectPacket(SshErrorCode reason,
+ const QByteArray &reasonString);
+ void sendMsgUnimplementedPacket(quint32 serverSeqNr);
+ void sendUserAuthServiceRequestPacket();
+ void sendUserAuthByPwdRequestPacket(const QByteArray &user,
+ const QByteArray &service, const QByteArray &pwd);
+ void sendUserAuthByKeyRequestPacket(const QByteArray &user,
+ const QByteArray &service);
+ void sendRequestFailurePacket();
+ void sendSessionPacket(quint32 channelId, quint32 windowSize,
+ quint32 maxPacketSize);
+ void sendEnvPacket(quint32 remoteChannel, const QByteArray &var,
+ const QByteArray &value);
+ void sendExecPacket(quint32 remoteChannel, const QByteArray &command);
+ 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);
+
+private:
+ void sendPacket();
+
+ quint32 m_clientSeqNr;
+ SshEncryptionFacility m_encrypter;
+ QTcpSocket *m_socket;
+ SshOutgoingPacket m_outgoingPacket;
+};
+
+} // namespace Internal
+} // namespace Core
+
+#endif // SSHCONNECTIONOUTSTATE_P_H
diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h
index 0f7ba09523..c542087c25 100644
--- a/src/plugins/debugger/debuggerengine.h
+++ b/src/plugins/debugger/debuggerengine.h
@@ -87,7 +87,7 @@ public:
QString dumperLibrary;
QStringList dumperLibraryLocations;
- Core::SshServerInfo sshserver;
+ Core::SshConnectionParameters connParams;
DebuggerStartMode startMode;
};
diff --git a/src/plugins/debugger/gdb/remotegdbprocess.cpp b/src/plugins/debugger/gdb/remotegdbprocess.cpp
index fb7e2d0281..7288f44c7b 100644
--- a/src/plugins/debugger/gdb/remotegdbprocess.cpp
+++ b/src/plugins/debugger/gdb/remotegdbprocess.cpp
@@ -33,14 +33,15 @@
#include <ctype.h>
+using namespace Core;
+
namespace Debugger {
namespace Internal {
-RemoteGdbProcess::RemoteGdbProcess(const Core::SshServerInfo &server,
+RemoteGdbProcess::RemoteGdbProcess(const Core::SshConnectionParameters &connParams,
RemotePlainGdbAdapter *adapter, QObject *parent)
- : AbstractGdbProcess(parent), m_serverInfo(server), m_adapter(adapter)
+ : AbstractGdbProcess(parent), m_connParams(connParams), m_adapter(adapter)
{
-
}
QByteArray RemoteGdbProcess::readAllStandardOutput()
@@ -59,68 +60,122 @@ QByteArray RemoteGdbProcess::readAllStandardError()
void RemoteGdbProcess::start(const QString &cmd, const QStringList &args)
{
- m_gdbConn = Core::InteractiveSshConnection::create(m_serverInfo);
- m_appOutputConn = Core::InteractiveSshConnection::create(m_serverInfo);
- m_errOutputConn = Core::InteractiveSshConnection::create(m_serverInfo);
m_command = cmd;
m_cmdArgs = args;
- m_errOutputConn->start();
- m_appOutputConn->start();
- m_gdbConn->start();
- }
+ m_gdbStarted = false;
+ m_error.clear();
+ m_conn = SshConnection::create();
+ connect(m_conn.data(), SIGNAL(connected()), this, SLOT(handleConnected()));
+ connect(m_conn.data(), SIGNAL(error(SshError)), this,
+ SLOT(handleConnectionError()));
+ m_conn->connectToHost(m_connParams);
+}
-bool RemoteGdbProcess::waitForStarted()
+void RemoteGdbProcess::handleConnected()
{
- if (!waitForInputReady(m_appOutputConn))
- return false;
- if (!sendAndWaitForEcho(m_appOutputConn, readerCmdLine(AppOutputFile)))
- return false;
- if (!waitForInputReady(m_errOutputConn))
- return false;
- if (!sendAndWaitForEcho(m_errOutputConn, readerCmdLine(ErrOutputFile)))
- return false;
- if (!waitForInputReady(m_gdbConn))
- return false;
- connect(m_appOutputConn.data(), SIGNAL(remoteOutputAvailable()),
- this, SLOT(handleAppOutput()));
- connect(m_errOutputConn.data(), SIGNAL(remoteOutputAvailable()),
- this, SLOT(handleErrOutput()));
- connect(m_gdbConn.data(), SIGNAL(remoteOutputAvailable()),
- this, SLOT(handleGdbOutput()));
- m_gdbStarted = false;
- m_gdbCmdLine = "stty -echo && DISPLAY=:0.0 " + m_command.toUtf8() + ' '
+ m_fifoCreator = m_conn->createRemoteProcess( "rm -f "
+ + AppOutputFile + " && mkfifo " + AppOutputFile);
+ connect(m_fifoCreator.data(), SIGNAL(closed(int)), this,
+ SLOT(handleFifoCreationFinished(int)));
+ m_fifoCreator->start();
+}
+
+void RemoteGdbProcess::handleConnectionError()
+{
+ emitErrorExit(tr("Connection could not be established."));
+}
+
+void RemoteGdbProcess::handleFifoCreationFinished(int exitStatus)
+{
+ if (exitStatus != SshRemoteProcess::ExitedNormally) {
+ emitErrorExit(tr("Could not create FIFO."));
+ } else {
+ m_appOutputReader = m_conn->createRemoteProcess("cat " + AppOutputFile);
+ connect(m_appOutputReader.data(), SIGNAL(started()), this,
+ SLOT(handleAppOutputReaderStarted()));
+ connect(m_appOutputReader.data(), SIGNAL(closed(int)), this,
+ SLOT(handleAppOutputReaderFinished(int)));
+ m_appOutputReader->start();
+ }
+}
+
+void RemoteGdbProcess::handleAppOutputReaderStarted()
+{
+ connect(m_appOutputReader.data(), SIGNAL(outputAvailable(QByteArray)),
+ this, SLOT(handleAppOutput(QByteArray)));
+ QByteArray cmdLine = "DISPLAY=:0.0 " + m_command.toUtf8() + ' '
+ m_cmdArgs.join(QLatin1String(" ")).toUtf8()
- + " -tty=" + AppOutputFile + " 2>" + ErrOutputFile + '\n';
+ + " -tty=" + AppOutputFile;
if (!m_wd.isEmpty())
- m_gdbCmdLine.prepend("cd " + m_wd.toUtf8() + " && ");
- if (sendInput(m_gdbCmdLine) != m_gdbCmdLine.count())
- return false;
+ cmdLine.prepend("cd " + m_wd.toUtf8() + " && ");
+ m_gdbProc = m_conn->createRemoteProcess(cmdLine);
+ connect(m_gdbProc.data(), SIGNAL(started()), this,
+ SLOT(handleGdbStarted()));
+ connect(m_gdbProc.data(), SIGNAL(closed(int)), this,
+ SLOT(handleGdbFinished(int)));
+ connect(m_gdbProc.data(), SIGNAL(outputAvailable(QByteArray)), this,
+ SLOT(handleGdbOutput(QByteArray)));
+ connect(m_gdbProc.data(), SIGNAL(errorOutputAvailable(QByteArray)), this,
+ SLOT(handleErrOutput(QByteArray)));
+ m_gdbProc->start();
+}
+
+void RemoteGdbProcess::handleAppOutputReaderFinished(int exitStatus)
+{
+ if (exitStatus != SshRemoteProcess::ExitedNormally)
+ emitErrorExit(tr("Application output reader unexpectedly finished."));
+}
- return true;
+void RemoteGdbProcess::handleGdbStarted()
+{
+ m_gdbStarted = true;
+}
+
+void RemoteGdbProcess::handleGdbFinished(int exitStatus)
+{
+ switch (exitStatus) {
+ case SshRemoteProcess::FailedToStart:
+ emitErrorExit(tr("Remote gdb failed to start."));
+ break;
+ case SshRemoteProcess::KilledBySignal:
+ emitErrorExit(tr("Remote gdb crashed."));
+ break;
+ case SshRemoteProcess::ExitedNormally:
+ emit finished(m_gdbProc->exitCode(), QProcess::NormalExit);
+ break;
+ }
+ disconnect(m_conn.data(), 0, this, 0);
+ m_gdbProc = SshRemoteProcess::Ptr();
+ m_appOutputReader = SshRemoteProcess::Ptr();
+ m_conn->disconnectFromHost();
+}
+
+bool RemoteGdbProcess::waitForStarted()
+{
+ return m_error.isEmpty();
}
qint64 RemoteGdbProcess::write(const QByteArray &data)
{
- if (!m_gdbStarted || !m_inputToSend.isEmpty() || !m_lastSeqNr.isEmpty()) {
+ if (!m_gdbStarted || !m_inputToSend.isEmpty() || !m_lastSeqNr.isEmpty())
m_inputToSend.enqueue(data);
- return data.size();
- } else {
- return sendInput(data);
- }
+ else
+ sendInput(data);
+ return data.size();
}
void RemoteGdbProcess::kill()
{
- stopReaders();
- Core::InteractiveSshConnection::Ptr controlConn
- = Core::InteractiveSshConnection::create(m_serverInfo);
- if (!controlConn->hasError()) {
- if (controlConn->start())
- controlConn->sendInput("pkill -x gdb\r\n");
- }
+ SshRemoteProcess::Ptr killProc
+ = m_conn->createRemoteProcess("pkill -SIGKILL -x gdb");
+ killProc->start();
+}
- m_gdbConn->quit();
- emit finished(0, QProcess::CrashExit);
+void RemoteGdbProcess::interruptInferior()
+{
+ SshRemoteProcess::Ptr intProc
+ = m_conn->createRemoteProcess("pkill -x -SIGINT gdb");
+ intProc->start();
}
QProcess::ProcessState RemoteGdbProcess::state() const
@@ -130,35 +185,19 @@ QProcess::ProcessState RemoteGdbProcess::state() const
QString RemoteGdbProcess::errorString() const
{
- return m_gdbConn ? m_gdbConn->error() : QString();
+ return m_error;
}
-void RemoteGdbProcess::handleGdbOutput()
+void RemoteGdbProcess::handleGdbOutput(const QByteArray &output)
{
- m_currentGdbOutput
- += removeCarriageReturn(m_gdbConn->waitForRemoteOutput(0));
+ // TODO: Carriage return removal still necessary?
+ m_currentGdbOutput += removeCarriageReturn(output);
#if 0
qDebug("%s: complete unread output is '%s'", Q_FUNC_INFO, m_currentGdbOutput.data());
#endif
- if (checkForGdbExit(m_currentGdbOutput)) {
- m_currentGdbOutput.clear();
- return;
- }
-
if (!m_currentGdbOutput.endsWith('\n'))
return;
- if (!m_gdbStarted) {
- const int index = m_currentGdbOutput.indexOf(m_gdbCmdLine);
- if (index != -1)
- m_currentGdbOutput.remove(index, m_gdbCmdLine.size());
- // Note: We can't guarantee that we will match the command line,
- // because the remote terminal sometimes inserts control characters.
- // Otherwise we could set m_gdbStarted here.
- }
-
- m_gdbStarted = true;
-
if (m_currentGdbOutput.contains(m_lastSeqNr + '^'))
m_lastSeqNr.clear();
@@ -187,7 +226,7 @@ QProcessEnvironment RemoteGdbProcess::processEnvironment() const
void RemoteGdbProcess::setProcessEnvironment(const QProcessEnvironment & /* env */)
{
- // TODO: Do something.
+ // TODO: Do something. (if remote process exists: set, otherwise queue)
}
void RemoteGdbProcess::setEnvironment(const QStringList & /* env */)
@@ -211,48 +250,27 @@ int RemoteGdbProcess::findAnchor(const QByteArray &data) const
return -1;
}
-qint64 RemoteGdbProcess::sendInput(const QByteArray &data)
+void RemoteGdbProcess::sendInput(const QByteArray &data)
{
int pos;
for (pos = 0; pos < data.size(); ++pos)
if (!isdigit(data.at(pos)))
break;
m_lastSeqNr = data.left(pos);
- return m_gdbConn->sendInput(data) ? data.size() : 0;
+ m_gdbProc->sendInput(data);
}
-void RemoteGdbProcess::handleAppOutput()
+void RemoteGdbProcess::handleAppOutput(const QByteArray &output)
{
- m_adapter->handleApplicationOutput(m_appOutputConn->waitForRemoteOutput(0));
+ m_adapter->handleApplicationOutput(output);
}
-void RemoteGdbProcess::handleErrOutput()
+void RemoteGdbProcess::handleErrOutput(const QByteArray &output)
{
- m_errorOutput += m_errOutputConn->waitForRemoteOutput(0);
+ m_errorOutput += output;
emit readyReadStandardError();
}
-void RemoteGdbProcess::stopReaders()
-{
- if (m_appOutputConn) {
- disconnect(m_appOutputConn.data(), SIGNAL(remoteOutputAvailable()),
- this, SLOT(handleAppOutput()));
- m_appOutputConn->sendInput(CtrlC);
- m_appOutputConn->quit();
- }
- if (m_errOutputConn) {
- disconnect(m_errOutputConn.data(), SIGNAL(remoteOutputAvailable()),
- this, SLOT(handleErrOutput()));
- m_errOutputConn->sendInput(CtrlC);
- m_errOutputConn->quit();
- }
-}
-
-QByteArray RemoteGdbProcess::readerCmdLine(const QByteArray &file)
-{
- return "rm -f " + file + " && mkfifo " + file + " && cat " + file + "\r\n";
-}
-
QByteArray RemoteGdbProcess::removeCarriageReturn(const QByteArray &data)
{
QByteArray output;
@@ -264,48 +282,16 @@ QByteArray RemoteGdbProcess::removeCarriageReturn(const QByteArray &data)
return output;
}
-bool RemoteGdbProcess::checkForGdbExit(QByteArray &output)
+void RemoteGdbProcess::emitErrorExit(const QString &error)
{
- const QByteArray exitString("^exit");
- const int exitPos = output.indexOf(exitString);
- if (exitPos == -1)
- return false;
-
- emit finished(0, QProcess::NormalExit);
- disconnect(m_gdbConn.data(), SIGNAL(remoteOutputAvailable()),
- this, SLOT(handleGdbOutput()));
- output.remove(exitPos + exitString.size(), output.size());
- stopReaders();
- return true;
-}
-
-bool RemoteGdbProcess::waitForInputReady(Core::InteractiveSshConnection::Ptr &conn)
-{
- if (conn->waitForRemoteOutput(m_serverInfo.timeout).isEmpty())
- return false;
- while (!conn->waitForRemoteOutput(100).isEmpty())
- ;
- return true;
-}
-
-bool RemoteGdbProcess::sendAndWaitForEcho(Core::InteractiveSshConnection::Ptr &conn,
- const QByteArray &cmdLine)
-{
- conn->sendInput(cmdLine);
- QByteArray allOutput;
- while (!allOutput.endsWith(cmdLine)) {
- const QByteArray curOutput = conn->waitForRemoteOutput(100);
- if (curOutput.isEmpty())
- return false;
- allOutput += curOutput;
+ if (m_error.isEmpty()) {
+ m_error = error;
+ emit finished(-1, QProcess::CrashExit);
}
- return true;
}
-
const QByteArray RemoteGdbProcess::CtrlC = QByteArray(1, 0x3);
const QByteArray RemoteGdbProcess::AppOutputFile("app_output");
-const QByteArray RemoteGdbProcess::ErrOutputFile("err_output");
} // namespace Internal
} // namespace Debugger
diff --git a/src/plugins/debugger/gdb/remotegdbprocess.h b/src/plugins/debugger/gdb/remotegdbprocess.h
index 1008b334da..9a9679b695 100644
--- a/src/plugins/debugger/gdb/remotegdbprocess.h
+++ b/src/plugins/debugger/gdb/remotegdbprocess.h
@@ -33,6 +33,7 @@
#include "abstractgdbprocess.h"
#include <coreplugin/ssh/sshconnection.h>
+#include <coreplugin/ssh/sshremoteprocess.h>
#include <QtCore/QByteArray>
#include <QtCore/QQueue>
@@ -46,7 +47,7 @@ class RemoteGdbProcess : public AbstractGdbProcess
{
Q_OBJECT
public:
- RemoteGdbProcess(const Core::SshServerInfo &server,
+ RemoteGdbProcess(const Core::SshConnectionParameters &server,
RemotePlainGdbAdapter *adapter, QObject *parent = 0);
virtual QByteArray readAllStandardOutput();
@@ -65,32 +66,37 @@ public:
virtual void setEnvironment(const QStringList &env);
virtual void setWorkingDirectory(const QString &dir);
+ void interruptInferior();
+
static const QByteArray CtrlC;
private slots:
- void handleGdbOutput();
- void handleAppOutput();
- void handleErrOutput();
+ void handleConnected();
+ void handleConnectionError();
+ void handleFifoCreationFinished(int exitStatus);
+ void handleAppOutputReaderStarted();
+ void handleAppOutputReaderFinished(int exitStatus);
+ void handleGdbStarted();
+ void handleGdbFinished(int exitStatus);
+ void handleGdbOutput(const QByteArray &output);
+ void handleAppOutput(const QByteArray &output);
+ void handleErrOutput(const QByteArray &output);
private:
static QByteArray readerCmdLine(const QByteArray &file);
int findAnchor(const QByteArray &data) const;
- qint64 sendInput(const QByteArray &data);
- void stopReaders();
+ void sendInput(const QByteArray &data);
QByteArray removeCarriageReturn(const QByteArray &data);
- bool checkForGdbExit(QByteArray &output);
- bool sendAndWaitForEcho(Core::InteractiveSshConnection::Ptr &conn,
- const QByteArray &cmdLine);
- bool waitForInputReady(Core::InteractiveSshConnection::Ptr &conn);
+ void emitErrorExit(const QString &error);
static const QByteArray AppOutputFile;
- static const QByteArray ErrOutputFile;
- Core::SshServerInfo m_serverInfo;
- Core::InteractiveSshConnection::Ptr m_gdbConn;
- Core::InteractiveSshConnection::Ptr m_appOutputConn;
- Core::InteractiveSshConnection::Ptr m_errOutputConn;
+ Core::SshConnectionParameters m_connParams;
+ Core::SshConnection::Ptr m_conn;
+ Core::SshRemoteProcess::Ptr m_gdbProc;
+ Core::SshRemoteProcess::Ptr m_appOutputReader;
+ Core::SshRemoteProcess::Ptr m_fifoCreator;
QByteArray m_gdbOutput;
QByteArray m_errorOutput;
QString m_command;
@@ -99,7 +105,7 @@ private:
QQueue<QByteArray> m_inputToSend;
QByteArray m_currentGdbOutput;
QByteArray m_lastSeqNr;
- QByteArray m_gdbCmdLine;
+ QString m_error;
bool m_gdbStarted;
RemotePlainGdbAdapter *m_adapter;
diff --git a/src/plugins/debugger/gdb/remoteplaingdbadapter.cpp b/src/plugins/debugger/gdb/remoteplaingdbadapter.cpp
index 2f51918883..88946c1136 100644
--- a/src/plugins/debugger/gdb/remoteplaingdbadapter.cpp
+++ b/src/plugins/debugger/gdb/remoteplaingdbadapter.cpp
@@ -40,7 +40,7 @@ namespace Internal {
RemotePlainGdbAdapter::RemotePlainGdbAdapter(GdbEngine *engine,
QObject *parent)
: AbstractPlainGdbAdapter(engine, parent),
- m_gdbProc(engine->startParameters().sshserver, this)
+ m_gdbProc(engine->startParameters().connParams, this)
{
}
@@ -60,7 +60,7 @@ void RemotePlainGdbAdapter::startAdapter()
void RemotePlainGdbAdapter::interruptInferior()
{
- m_gdbProc.write(RemoteGdbProcess::CtrlC);
+ m_gdbProc.interruptInferior();
}
QByteArray RemotePlainGdbAdapter::execFilePath() const
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.cpp
index eddb077acd..575da23144 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.cpp
@@ -35,38 +35,14 @@
#include "maemoconfigtestdialog.h"
#include "ui_maemoconfigtestdialog.h"
-#include "maemosshthread.h"
+#include "maemodeviceconfigurations.h"
-#include <QtGui/QPushButton>
-
-namespace {
-
-/**
- * Class that waits until a thread is finished and then deletes it, and then
- * schedules itself to be deleted.
- */
-class SafeThreadDeleter : public QThread
-{
-public:
- SafeThreadDeleter(QThread *thread) : m_thread(thread) {}
- ~SafeThreadDeleter() { wait(); }
-
-protected:
- void run()
- {
- // Wait for m_thread to finish and then delete it
- m_thread->wait();
- delete m_thread;
-
- // Schedule this thread for deletion
- deleteLater();
- }
+#include <coreplugin/ssh/sshconnection.h>
+#include <coreplugin/ssh/sshremoteprocess.h>
-private:
- QThread *m_thread;
-};
+#include <QtGui/QPushButton>
-} // anonymous namespace
+using namespace Core;
namespace Qt4ProjectManager {
namespace Internal {
@@ -75,7 +51,6 @@ MaemoConfigTestDialog::MaemoConfigTestDialog(const MaemoDeviceConfig &config, QW
: QDialog(parent)
, m_ui(new Ui_MaemoConfigTestDialog)
, m_config(config)
- , m_deviceTester(0)
{
setAttribute(Qt::WA_DeleteOnClose);
@@ -94,65 +69,86 @@ MaemoConfigTestDialog::~MaemoConfigTestDialog()
void MaemoConfigTestDialog::startConfigTest()
{
- if (m_deviceTester)
+ if (m_testProcess)
return;
m_ui->testResultEdit->setPlainText(tr("Testing configuration..."));
m_closeButton->setText(tr("Stop Test"));
+ m_connection = SshConnection::create();
+ connect(m_connection.data(), SIGNAL(connected()), this,
+ SLOT(handleConnected()));
+ connect(m_connection.data(), SIGNAL(error(SshError)), this,
+ SLOT(handleConnectionError()));
+ m_connection->connectToHost(m_config.server);
+}
+void MaemoConfigTestDialog::handleConnected()
+{
+ if (!m_connection)
+ return;
QLatin1String sysInfoCmd("uname -rsm");
QLatin1String qtInfoCmd("dpkg -l |grep libqt "
"|sed 's/[[:space:]][[:space:]]*/ /g' "
"|cut -d ' ' -f 2,3 |sed 's/~.*//g'");
QString command(sysInfoCmd + " && " + qtInfoCmd);
- m_deviceTester = new MaemoSshRunner(m_config.server, command);
- connect(m_deviceTester, SIGNAL(remoteOutput(QString)),
- this, SLOT(processSshOutput(QString)));
- connect(m_deviceTester, SIGNAL(finished()),
- this, SLOT(handleTestThreadFinished()));
+ m_testProcess = m_connection->createRemoteProcess(command.toUtf8());
+ connect(m_testProcess.data(), SIGNAL(closed(int)), this,
+ SLOT(handleProcessFinished(int)));
+ connect(m_testProcess.data(), SIGNAL(outputAvailable(QByteArray)), this,
+ SLOT(processSshOutput(QByteArray)));
+ m_testProcess->start();
+}
- m_deviceTester->start();
+void MaemoConfigTestDialog::handleConnectionError()
+{
+ if (!m_connection)
+ return;
+ QString output = tr("Could not connect to host: %1")
+ .arg(m_connection->errorString());
+ if (m_config.type == MaemoDeviceConfig::Simulator)
+ output += tr("\nDid you start Qemu?");
+ m_ui->testResultEdit->setPlainText(output);
+ stopConfigTest();
}
-void MaemoConfigTestDialog::handleTestThreadFinished()
+void MaemoConfigTestDialog::handleProcessFinished(int exitStatus)
{
- if (!m_deviceTester)
+ Q_ASSERT(exitStatus == SshRemoteProcess::FailedToStart
+ || exitStatus == SshRemoteProcess::KilledBySignal
+ || exitStatus == SshRemoteProcess::ExitedNormally);
+
+ if (!m_testProcess)
return;
- QString output;
- if (m_deviceTester->hasError()) {
- output = tr("Device configuration test failed:\n%1").arg(m_deviceTester->error());
- if (m_config.type == MaemoDeviceConfig::Simulator)
- output.append(tr("\nDid you start Qemu?"));
+ if (exitStatus != SshRemoteProcess::ExitedNormally
+ || m_testProcess->exitCode() != 0) {
+ m_ui->testResultEdit->setPlainText(tr("Remote process failed: %1")
+ .arg(m_testProcess->errorString()));
} else {
- output = parseTestOutput();
+ const QString &output = parseTestOutput();
if (!m_qtVersionOk) {
m_ui->errorLabel->setText(tr("Qt version mismatch! "
" Expected Qt on device: 4.6.2 or later."));
}
+ m_ui->testResultEdit->setPlainText(output);
}
- m_ui->testResultEdit->setPlainText(output);
stopConfigTest();
}
void MaemoConfigTestDialog::stopConfigTest()
{
- if (m_deviceTester) {
- m_deviceTester->disconnect(); // Disconnect signals
- m_deviceTester->stop();
+ if (m_testProcess)
+ disconnect(m_testProcess.data(), 0, this, 0);
+ if (m_connection)
+ disconnect(m_connection.data(), 0, this, 0);
- SafeThreadDeleter *deleter = new SafeThreadDeleter(m_deviceTester);
- deleter->start();
-
- m_deviceTester = 0;
- m_deviceTestOutput.clear();
- m_closeButton->setText(tr("Close"));
- }
+ m_deviceTestOutput.clear();
+ m_closeButton->setText(tr("Close"));
}
-void MaemoConfigTestDialog::processSshOutput(const QString &data)
+void MaemoConfigTestDialog::processSshOutput(const QByteArray &output)
{
- m_deviceTestOutput.append(data);
+ m_deviceTestOutput.append(QString::fromUtf8(output));
}
QString MaemoConfigTestDialog::parseTestOutput()
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.h b/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.h
index 8bc2d63930..a6ec321ca2 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.h
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.h
@@ -35,18 +35,23 @@
#ifndef MAEMOCONFIGTESTDIALOG_H
#define MAEMOCONFIGTESTDIALOG_H
-#include <QDialog>
+#include <QtCore/QSharedPointer>
+#include <QtGui/QDialog>
QT_BEGIN_NAMESPACE
class QPushButton;
class Ui_MaemoConfigTestDialog;
QT_END_NAMESPACE
+namespace Core {
+ class SshConnection;
+ class SshRemoteProcess;
+} // namespace Core
+
namespace Qt4ProjectManager {
namespace Internal {
class MaemoDeviceConfig;
-class MaemoSshRunner;
/**
* A dialog that runs a test of a device configuration.
@@ -60,8 +65,10 @@ public:
private slots:
void stopConfigTest();
- void processSshOutput(const QString &data);
- void handleTestThreadFinished();
+ void processSshOutput(const QByteArray &output);
+ void handleConnected();
+ void handleConnectionError();
+ void handleProcessFinished(int exitStatus);
private:
void startConfigTest();
@@ -71,7 +78,8 @@ private:
QPushButton *m_closeButton;
const MaemoDeviceConfig &m_config;
- MaemoSshRunner *m_deviceTester;
+ QSharedPointer<Core::SshConnection> m_connection;
+ QSharedPointer<Core::SshRemoteProcess> m_testProcess;
QString m_deviceTestOutput;
bool m_qtVersionOk;
};
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp
index 7ff2747ddf..d1d7d6645e 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp
@@ -42,7 +42,7 @@
#include <algorithm>
-typedef Core::SshServerInfo::AuthType AuthType;
+typedef Core::SshConnectionParameters::AuthType AuthType;
namespace Qt4ProjectManager {
namespace Internal {
@@ -80,7 +80,7 @@ namespace {
const QString DefaultHostNameHW(QLatin1String("192.168.2.15"));
const QString DefaultHostNameSim(QLatin1String("localhost"));
const QString DefaultUserName(QLatin1String("developer"));
- const AuthType DefaultAuthType(Core::SshServerInfo::AuthByKey);
+ const AuthType DefaultAuthType(Core::SshConnectionParameters::AuthByKey);
const int DefaultTimeout(30);
const MaemoDeviceConfig::DeviceType DefaultDeviceType(MaemoDeviceConfig::Physical);
};
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h
index 7ffa28251e..43ea377006 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h
@@ -60,7 +60,7 @@ public:
void save(QSettings &settings) const;
bool isValid() const;
- Core::SshServerInfo server;
+ Core::SshConnectionParameters server;
QString name;
DeviceType type;
int gdbServerPort;
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp
index c173ddb607..4d5a3cbc57 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp
@@ -40,6 +40,9 @@
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/progressmanager.h>
+#include <coreplugin/ssh/sftpchannel.h>
+#include <coreplugin/ssh/sshconnection.h>
+#include <coreplugin/ssh/sshremoteprocess.h>
#include <debugger/debuggerengine.h>
#include <debugger/debuggerplugin.h>
#include <debugger/debuggerrunner.h>
@@ -57,6 +60,8 @@
#include <QtGui/QMessageBox>
+using namespace Core;
+
namespace Qt4ProjectManager {
namespace Internal {
@@ -78,152 +83,278 @@ AbstractMaemoRunControl::~AbstractMaemoRunControl()
{
}
+ // TODO: We can cache the connection. We'd have to check if the connection
+ // is active and its parameters are the same as m_devConfig. If yes,
+ // skip the connection step and jump right to handleConnected()
void AbstractMaemoRunControl::start()
{
- m_stoppedByUser = false;
if (!m_devConfig.isValid()) {
handleError(tr("No device configuration set for run configuration."));
} else {
+ m_stopped = false;
emit started();
- startInitialCleanup();
+ m_connection = SshConnection::create();
+ connect(m_connection.data(), SIGNAL(connected()), this,
+ SLOT(handleConnected()));
+ connect(m_connection.data(), SIGNAL(error(SshError)), this,
+ SLOT(handleConnectionFailure()));
+ m_connection->connectToHost(m_devConfig.server);
}
}
-void AbstractMaemoRunControl::startInitialCleanup()
-{
+void AbstractMaemoRunControl::handleConnected()
+{
+ if (m_stopped)
+ return;
+
emit appendMessage(this, tr("Cleaning up remote leftovers first ..."), false);
- const QStringList appsToKill
- = QStringList() << executableFileName() << QLatin1String("gdbserver");
+ const QStringList appsToKill = QStringList() << executableFileName()
+#ifdef USE_GDBSERVER
+ << QLatin1String("gdbserver");
+#else
+ << QLatin1String("gdb");
+#endif
killRemoteProcesses(appsToKill, true);
}
-void AbstractMaemoRunControl::stop()
+void AbstractMaemoRunControl::handleConnectionFailure()
{
- m_stoppedByUser = true;
- if (isCleaning())
- m_initialCleaner->stop();
- else if (isDeploying())
- m_sshDeployer->stop();
- else
- stopInternal();
+ if (m_stopped)
+ return;
+
+ handleError(tr("Could not connect to host: %1")
+ .arg(m_connection->errorString()));
+ emit finished();
}
-void AbstractMaemoRunControl::handleInitialCleanupFinished()
+void AbstractMaemoRunControl::stop()
{
- if (m_stoppedByUser) {
- emit appendMessage(this, tr("Initial cleanup canceled by user."), false);
+ m_stopped = true;
+ if (m_connection && m_connection->state() == SshConnection::Connecting) {
+ disconnect(m_connection.data(), 0, this, 0);
+ m_connection->disconnectFromHost();
+ emit finished();
+ } else if (m_initialCleaner && m_initialCleaner->isRunning()) {
+ disconnect(m_initialCleaner.data(), 0, this, 0);
emit finished();
- } else if (m_initialCleaner->hasError()) {
- handleError(tr("Error running initial cleanup: %1")
- .arg(m_initialCleaner->error()));
+ } else if (m_installer && m_installer->isRunning()) {
+ disconnect(m_installer.data(), 0, this, 0);
+ emit finished();
+ } else if (isDeploying()) {
+ m_uploadsInProgress.clear();
+ m_linksInProgress.clear();
+ disconnect(m_uploader.data(), 0, this, 0);
+ m_progress.reportCanceled();
+ m_progress.reportFinished();
emit finished();
} else {
- emit appendMessage(this, tr("Initial cleanup done."), false);
- startInternal();
+ stopInternal();
}
}
-void AbstractMaemoRunControl::startDeployment(bool forDebugging)
+void AbstractMaemoRunControl::startDeployment()
{
QTC_ASSERT(m_runConfig, return);
- if (m_stoppedByUser) {
- emit finished();
- } else {
- m_needsInstall = false;
- m_deployables.clear();
- m_remoteLinks.clear();
- const MaemoPackageCreationStep * const packageStep
- = m_runConfig->packageStep();
- if (packageStep->isPackagingEnabled()) {
- const MaemoDeployable d(packageFilePath(), uploadDir());
- m_needsInstall = addDeployableIfNeeded(d);
- } else {
- const MaemoDeployables * const deployables
- = packageStep->deployables();
- const int deployableCount = deployables->deployableCount();
- for (int i = 0; i < deployableCount; ++i) {
- const MaemoDeployable &d = deployables->deployableAt(i);
- if (addDeployableIfNeeded(d))
- m_needsInstall = true;
- }
- }
-
- if (forDebugging) {
- QFileInfo dumperInfo(m_runConfig->dumperLib());
- if (dumperInfo.exists()) {
- const MaemoDeployable d(m_runConfig->dumperLib(), uploadDir());
- m_needsInstall = addDeployableIfNeeded(d);
- }
- }
- deploy();
- }
-}
-
-bool AbstractMaemoRunControl::addDeployableIfNeeded(const MaemoDeployable &deployable)
-{
- if (m_runConfig->currentlyNeedsDeployment(m_devConfig.server.host,
- deployable)) {
- const QString fileName
- = QFileInfo(deployable.localFilePath).fileName();
- const QString remoteFilePath = deployable.remoteDir + '/' + fileName;
- const QString uploadFilePath =uploadDir() + '/' + fileName + '.'
+ m_uploader = m_connection->createSftpChannel();
+ connect(m_uploader.data(), SIGNAL(initialized()), this,
+ SLOT(handleSftpChannelInitialized()));
+ connect(m_uploader.data(), SIGNAL(initializationFailed(QString)), this,
+ SLOT(handleSftpChannelInitializationFailed(QString)));
+ connect(m_uploader.data(), SIGNAL(finished(Core::SftpJobId, QString)),
+ this, SLOT(handleSftpJobFinished(Core::SftpJobId, QString)));
+ m_uploader->initialize();
+}
+
+void AbstractMaemoRunControl::handleSftpChannelInitialized()
+{
+ if (m_stopped)
+ return;
+
+ m_uploadsInProgress.clear();
+ m_linksInProgress.clear();
+ m_needsInstall = false;
+ const QList<MaemoDeployable> &deployables = filesToDeploy();
+ foreach (const MaemoDeployable &d, deployables) {
+ const QString fileName = QFileInfo(d.localFilePath).fileName();
+ const QString remoteFilePath = d.remoteDir + '/' + fileName;
+ const QString uploadFilePath = uploadDir() + '/' + fileName + '.'
+ QCryptographicHash::hash(remoteFilePath.toUtf8(),
QCryptographicHash::Md5).toHex();
- m_deployables.append(Core::SftpTransferInfo(deployable.localFilePath,
- uploadFilePath.toUtf8(), Core::SftpTransferInfo::Upload));
- m_remoteLinks.insert(uploadFilePath, remoteFilePath);
- return true;
- } else {
- return false;
+ const SftpJobId job = m_uploader->uploadFile(d.localFilePath,
+ uploadFilePath, SftpOverwriteExisting);
+ if (job == SftpInvalidJob) {
+ handleError(tr("Upload failed: Could not open file '%1'")
+ .arg(d.localFilePath));
+ return;
+ }
+ emit appendMessage(this, tr("Started uploading file '%1'.")
+ .arg(d.localFilePath), false);
+ m_uploadsInProgress.insert(job, DeployInfo(d, uploadFilePath));
}
-}
-void AbstractMaemoRunControl::deploy()
-{
Core::ICore::instance()->progressManager()
->addTask(m_progress.future(), tr("Deploying"),
QLatin1String("Maemo.Deploy"));
- if (!m_deployables.isEmpty()) {
- QList<Core::SftpTransferInfo> deploySpecs;
- QStringList files;
- foreach (const Core::SftpTransferInfo &deployable, m_deployables)
- files << deployable.localFilePath;
- emit appendMessage(this,
- tr("Files to deploy: %1.").arg(files.join(" ")), false);
- m_sshDeployer.reset(new MaemoSshDeployer(m_devConfig.server, m_deployables));
- connect(m_sshDeployer.data(), SIGNAL(finished()),
- this, SLOT(handleDeployThreadFinished()));
- connect(m_sshDeployer.data(), SIGNAL(fileCopied(QString)),
- this, SLOT(handleFileCopied()));
- m_progress.setProgressRange(0, m_deployables.count());
+ if (!m_uploadsInProgress.isEmpty()) {
+ m_progress.setProgressRange(0, m_uploadsInProgress.count());
m_progress.setProgressValue(0);
m_progress.reportStarted();
- m_sshDeployer->start();
} else {
m_progress.reportFinished();
startExecutionIfPossible();
}
}
-void AbstractMaemoRunControl::handleFileCopied()
+void AbstractMaemoRunControl::handleSftpChannelInitializationFailed(const QString &error)
{
- const Core::SftpTransferInfo &deployable = m_deployables.takeFirst();
- const QString remoteDir
- = QFileInfo(m_remoteLinks.value(QString::fromUtf8(deployable.remoteFilePath)))
- .dir().path();
+ if (m_stopped)
+ return;
+ handleError(tr("Could not set up SFTP connection: %1").arg(error));
+}
- // TODO: This should be done after the linking step, in case the
- // operation is cancelled directly after the upload.
- m_runConfig->setDeployed(m_devConfig.server.host,
- MaemoDeployable(deployable.localFilePath, remoteDir));
+void AbstractMaemoRunControl::handleSftpJobFinished(Core::SftpJobId job,
+ const QString &error)
+{
+ if (m_stopped)
+ return;
+
+ QMap<SftpJobId, DeployInfo>::Iterator it = m_uploadsInProgress.find(job);
+ if (it == m_uploadsInProgress.end()) {
+ qWarning("%s: Job %u not found in map.", Q_FUNC_INFO, job);
+ return;
+ }
+
+ const DeployInfo &deployInfo = it.value();
+ if (!error.isEmpty()) {
+ handleError(tr("Failed to upload file %1: %2")
+ .arg(deployInfo.first.localFilePath, error));
+ return;
+ }
m_progress.setProgressValue(m_progress.progressValue() + 1);
+ appendMessage(this, tr("Successfully uploaded file '%1'.")
+ .arg(deployInfo.first.localFilePath), false);
+
+ const QString remoteFilePath = deployInfo.first.remoteDir + '/'
+ + QFileInfo(deployInfo.first.localFilePath).fileName();
+ QByteArray linkCommand = remoteSudo().toUtf8() + " ln -sf "
+ + deployInfo.second.toUtf8() + ' ' + remoteFilePath.toUtf8();
+ SshRemoteProcess::Ptr linkProcess
+ = m_connection->createRemoteProcess(linkCommand);
+ connect(linkProcess.data(), SIGNAL(closed(int)), this,
+ SLOT(handleLinkProcessFinished(int)));
+ m_linksInProgress.insert(linkProcess, deployInfo.first);
+ linkProcess->start();
+ m_uploadsInProgress.erase(it);
+}
+
+void AbstractMaemoRunControl::handleLinkProcessFinished(int exitStatus)
+{
+ if (m_stopped)
+ return;
+
+ SshRemoteProcess * const proc = static_cast<SshRemoteProcess *>(sender());
+
+ // TODO: List instead of map? We can't use it for lookup anyway.
+ QMap<SshRemoteProcess::Ptr, MaemoDeployable>::Iterator it;
+ for (it = m_linksInProgress.begin(); it != m_linksInProgress.end(); ++it) {
+ if (it.key().data() == proc)
+ break;
+ }
+ if (it == m_linksInProgress.end()) {
+ qWarning("%s: Remote process %p not found in process list.",
+ Q_FUNC_INFO, proc);
+ return;
+ }
+
+ const MaemoDeployable &deployable = it.value();
+ if (exitStatus != SshRemoteProcess::ExitedNormally
+ || proc->exitCode() != 0) {
+ handleError(tr("Deployment failed for file '%1': "
+ "Could not create link '%2' on remote system.")
+ .arg(deployable.localFilePath, deployable.remoteDir + '/'
+ + QFileInfo(deployable.localFilePath).fileName()));
+ return;
+ }
+
+ m_runConfig->setDeployed(m_devConfig.server.host, it.value());
+ m_linksInProgress.erase(it);
+ if (m_linksInProgress.isEmpty() && m_uploadsInProgress.isEmpty()) {
+ if (m_needsInstall) {
+ emit appendMessage(this, tr("Installing package ..."), false);
+ const QByteArray cmd = remoteSudo().toUtf8() + " dpkg -i "
+ + packageFileName().toUtf8();
+ m_installer = m_connection->createRemoteProcess(cmd);
+ connect(m_installer.data(), SIGNAL(closed(int)), this,
+ SLOT(handleInstallationFinished(int)));
+ connect(m_installer.data(), SIGNAL(outputAvailable(QByteArray)),
+ this, SLOT(handleRemoteOutput(QByteArray)));
+ connect(m_installer.data(),
+ SIGNAL(errorOutputAvailable(QByteArray)), this,
+ SLOT(handleRemoteErrorOutput(QByteArray)));
+ m_installer->start();
+ } else {
+ handleDeploymentFinished();
+ }
+ }
+}
+
+void AbstractMaemoRunControl::handleInstallationFinished(int exitStatus)
+{
+ if (m_stopped)
+ return;
+
+ if (exitStatus != SshRemoteProcess::ExitedNormally
+ || m_installer->exitCode() != 0) {
+ handleError(tr("Installing package failed."));
+ } else {
+ emit appendMessage(this, tr("Package installation finished."), false);
+ handleDeploymentFinished();
+ }
+}
+
+void AbstractMaemoRunControl::handleDeploymentFinished()
+{
+ emit appendMessage(this, tr("Deployment finished."), false);
+ m_progress.reportFinished();
+ startExecutionIfPossible();
+}
+
+QList<MaemoDeployable> AbstractMaemoRunControl::filesToDeploy()
+{
+ QList<MaemoDeployable> deployableList;
+ if (m_runConfig->packageStep()->isPackagingEnabled()) {
+ const MaemoDeployable d(packageFilePath(), uploadDir());
+ m_needsInstall = addDeployableIfNeeded(deployableList, d);
+ } else {
+ const MaemoDeployables * const deployables
+ = m_runConfig->packageStep()->deployables();
+ const int deployableCount = deployables->deployableCount();
+ for (int i = 0; i < deployableCount; ++i) {
+ const MaemoDeployable &d = deployables->deployableAt(i);
+ addDeployableIfNeeded(deployableList, d);
+ }
+ m_needsInstall = false;
+ }
+ return deployableList;
+}
+
+bool AbstractMaemoRunControl::addDeployableIfNeeded(QList<MaemoDeployable> &deployables,
+ const MaemoDeployable &deployable)
+{
+ if (m_runConfig->currentlyNeedsDeployment(m_devConfig.server.host,
+ deployable)) {
+ deployables << deployable;
+ return true;
+ } else {
+ return false;
+ }
}
bool AbstractMaemoRunControl::isDeploying() const
{
- return m_sshDeployer && m_sshDeployer->isRunning();
+ return !m_uploadsInProgress.isEmpty() || !m_linksInProgress.isEmpty();
}
QString AbstractMaemoRunControl::packageFileName() const
@@ -260,33 +391,68 @@ void AbstractMaemoRunControl::startExecutionIfPossible()
void AbstractMaemoRunControl::startExecution()
{
- m_sshRunner.reset(new MaemoSshRunner(m_devConfig.server, remoteCall()));
- connect(m_sshRunner.data(), SIGNAL(finished()),
- this, SLOT(handleRunThreadFinished()));
- connect(m_sshRunner.data(), SIGNAL(remoteOutput(QString)),
- this, SLOT(handleRemoteOutput(QString)));
+ m_runner = m_connection->createRemoteProcess(remoteCall().toUtf8());
+ connect(m_runner.data(), SIGNAL(started()), this,
+ SLOT(handleRemoteProcessStarted()));
+ connect(m_runner.data(), SIGNAL(closed(int)), this,
+ SLOT(handleRemoteProcessFinished(int)));
+ connect(m_runner.data(), SIGNAL(outputAvailable(QByteArray)), this,
+ SLOT(handleRemoteOutput(QByteArray)));
+ connect(m_runner.data(), SIGNAL(errorOutputAvailable(QByteArray)), this,
+ SLOT(handleRemoteErrorOutput(QByteArray)));
emit appendMessage(this, tr("Starting remote application."), false);
- m_sshRunner->start();
+ m_runner->start();
+}
+
+void AbstractMaemoRunControl::handleRemoteProcessFinished(int exitStatus)
+{
+ Q_ASSERT(exitStatus == SshRemoteProcess::FailedToStart
+ || exitStatus == SshRemoteProcess::KilledBySignal
+ || exitStatus == SshRemoteProcess::ExitedNormally);
+
+ if (m_stopped)
+ return;
+
+ if (exitStatus == SshRemoteProcess::ExitedNormally) {
+ emit appendMessage(this,
+ tr("Finished running remote process. Exit code was %1.")
+ .arg(m_runner->exitCode()), false);
+ emit finished();
+ } else {
+ handleError(tr("Error running remote process: %1")
+ .arg(m_runner->errorString()));
+ }
+}
+
+void AbstractMaemoRunControl::handleRemoteOutput(const QByteArray &output)
+{
+ emit addToOutputWindowInline(this, QString::fromUtf8(output), false);
+}
+
+void AbstractMaemoRunControl::handleRemoteErrorOutput(const QByteArray &output)
+{
+ emit addToOutputWindowInline(this, QString::fromUtf8(output), true);
}
bool AbstractMaemoRunControl::isRunning() const
{
- return isDeploying() || (m_sshRunner && m_sshRunner->isRunning());
+ return isDeploying() || (m_runner && m_runner->isRunning());
}
void AbstractMaemoRunControl::stopRunning(bool forDebugging)
{
- if (m_sshRunner && m_sshRunner->isRunning()) {
- m_sshRunner->stop();
+ if (m_runner && m_runner->isRunning()) {
+ disconnect(m_runner.data(), 0, this, 0);
QStringList apps(executableFileName());
if (forDebugging)
apps << QLatin1String("gdbserver");
killRemoteProcesses(apps, false);
+ emit finished();
}
}
void AbstractMaemoRunControl::killRemoteProcesses(const QStringList &apps,
- bool initialCleanup)
+ bool initialCleanup)
{
QString niceKill;
QString brutalKill;
@@ -296,54 +462,34 @@ void AbstractMaemoRunControl::killRemoteProcesses(const QStringList &apps,
}
QString remoteCall = niceKill + QLatin1String("sleep 1; ") + brutalKill;
remoteCall.remove(remoteCall.count() - 1, 1); // Get rid of trailing semicolon.
- QScopedPointer<MaemoSshRunner> &runner
- = initialCleanup ? m_initialCleaner : m_sshStopper;
- runner.reset(new MaemoSshRunner(m_devConfig.server, remoteCall));
- if (initialCleanup)
- connect(runner.data(), SIGNAL(finished()),
- this, SLOT(handleInitialCleanupFinished()));
- runner->start();
-}
-
-void AbstractMaemoRunControl::handleDeployThreadFinished()
-{
- bool cancel;
- if (m_stoppedByUser) {
- emit appendMessage(this, tr("Deployment canceled by user."), false);
- cancel = true;
- } else if (m_sshDeployer->hasError()) {
- handleError(tr("Deployment failed: %1").arg(m_sshDeployer->error()));
- cancel = true;
- } else {
- emit appendMessage(this, tr("Deployment finished."), false);
- cancel = false;
- }
-
- if (cancel) {
- m_progress.reportCanceled();
- m_progress.reportFinished();
- emit finished();
+ SshRemoteProcess::Ptr proc
+ = m_connection->createRemoteProcess(remoteCall.toUtf8());
+ if (initialCleanup) {
+ m_initialCleaner = proc;
+ connect(m_initialCleaner.data(), SIGNAL(closed(int)), this,
+ SLOT(handleInitialCleanupFinished(int)));
} else {
- m_progress.reportFinished();
- startExecutionIfPossible();
+ m_stopper = proc;
}
+ proc->start();
}
-void AbstractMaemoRunControl::handleRunThreadFinished()
+void AbstractMaemoRunControl::handleInitialCleanupFinished(int exitStatus)
{
- if (m_stoppedByUser) {
- emit appendMessage(this,
- tr("Remote execution canceled due to user request."),
- false);
- } else if (m_sshRunner && m_sshRunner->hasError()) {
- emit appendMessage(this, tr("Error running remote process: %1")
- .arg(m_sshRunner->error()),
- true);
+ Q_ASSERT(exitStatus == SshRemoteProcess::FailedToStart
+ || exitStatus == SshRemoteProcess::KilledBySignal
+ || exitStatus == SshRemoteProcess::ExitedNormally);
+
+ if (m_stopped)
+ return;
+
+ if (exitStatus != SshRemoteProcess::ExitedNormally) {
+ handleError(tr("Initial cleanup failed: %1")
+ .arg(m_initialCleaner->errorString()));
} else {
- emit appendMessage(this, tr("Finished running remote process."),
- false);
+ emit appendMessage(this, tr("Initial cleanup done."), false);
+ startDeployment();
}
- emit finished();
}
const QString AbstractMaemoRunControl::executableOnHost() const
@@ -366,32 +512,10 @@ QString AbstractMaemoRunControl::remoteSudo() const
return QLatin1String("/usr/lib/mad-developer/devrootsh");
}
-QString AbstractMaemoRunControl::remoteInstallCommand() const
-{
- Q_ASSERT(m_needsInstall);
- QString cmd;
- for (QMap<QString, QString>::ConstIterator it = m_remoteLinks.begin();
- it != m_remoteLinks.end(); ++it) {
- cmd += QString::fromLocal8Bit("%1 ln -sf %2 %3 && ")
- .arg(remoteSudo(), it.key(), it.value());
- }
- if (m_runConfig->packageStep()->isPackagingEnabled()) {
- cmd += QString::fromLocal8Bit("%1 dpkg -i %2").arg(remoteSudo())
- .arg(packageFileName());
- } else if (!m_remoteLinks.isEmpty()) {
- return cmd.remove(cmd.length() - 4, 4); // Trailing " && "
- }
-
- return cmd;
-}
-
const QString AbstractMaemoRunControl::targetCmdLinePrefix() const
{
- const QString &installPrefix = m_needsInstall
- ? remoteInstallCommand() + QLatin1String(" && ")
- : QString();
- return QString::fromLocal8Bit("%1%2 chmod a+x %3 && source /etc/profile && DISPLAY=:0.0 ")
- .arg(installPrefix).arg(remoteSudo()).arg(executableFilePathOnTarget());
+ return QString::fromLocal8Bit("%1 chmod a+x %2 && source /etc/profile && DISPLAY=:0.0 ")
+ .arg(remoteSudo()).arg(executableFilePathOnTarget());
}
QString AbstractMaemoRunControl::targetCmdLineSuffix() const
@@ -403,6 +527,15 @@ void AbstractMaemoRunControl::handleError(const QString &errString)
{
QMessageBox::critical(0, tr("Remote Execution Failure"), errString);
emit appendMessage(this, errString, true);
+ stop();
+}
+
+template<typename SshChannel> void AbstractMaemoRunControl::closeSshChannel(SshChannel &channel)
+{
+ if (channel) {
+ disconnect(channel.data(), 0, this, 0);
+ // channel->closeChannel();
+ }
}
@@ -416,11 +549,6 @@ MaemoRunControl::~MaemoRunControl()
stop();
}
-void MaemoRunControl::startInternal()
-{
- startDeployment(false);
-}
-
QString MaemoRunControl::remoteCall() const
{
return QString::fromLocal8Bit("%1 %2 %3").arg(targetCmdLinePrefix())
@@ -432,11 +560,6 @@ void MaemoRunControl::stopInternal()
AbstractMaemoRunControl::stopRunning(false);
}
-void MaemoRunControl::handleRemoteOutput(const QString &output)
-{
- emit addToOutputWindowInline(this, output, false);
-}
-
MaemoDebugRunControl::MaemoDebugRunControl(RunConfiguration *runConfiguration)
: AbstractMaemoRunControl(runConfiguration, ProjectExplorer::Constants::DEBUGMODE)
@@ -453,8 +576,9 @@ MaemoDebugRunControl::MaemoDebugRunControl(RunConfiguration *runConfiguration)
#else
m_startParams->startMode = StartRemoteGdb;
m_startParams->executable = executableFilePathOnTarget();
- m_startParams->debuggerCommand = QLatin1String("/usr/bin/gdb");
- m_startParams->sshserver = m_devConfig.server;
+ m_startParams->debuggerCommand = targetCmdLinePrefix()
+ + QLatin1String(" /usr/bin/gdb");
+ m_startParams->connParams = m_devConfig.server;
#endif
m_startParams->processArgs = m_runConfig->arguments();
m_startParams->sysRoot = m_runConfig->sysRoot();
@@ -475,12 +599,6 @@ MaemoDebugRunControl::~MaemoDebugRunControl()
stop();
}
-void MaemoDebugRunControl::startInternal()
-{
- m_debuggingStarted = false;
- startDeployment(true);
-}
-
QString MaemoDebugRunControl::remoteCall() const
{
return QString::fromLocal8Bit("%1 gdbserver :%2 %3 %4")
@@ -497,17 +615,6 @@ void MaemoDebugRunControl::startExecution()
#endif
}
-void MaemoDebugRunControl::handleRemoteOutput(const QString &output)
-{
-#ifdef USE_GDBSERVER
- if (!m_debuggingStarted) {
- m_debuggingStarted = true;
- startDebugging();
- }
-#endif
- emit addToOutputWindowInline(m_debuggerRunControl, output, false);
-}
-
void MaemoDebugRunControl::startDebugging()
{
DebuggerPlugin::startDebugger(m_debuggerRunControl);
@@ -529,10 +636,20 @@ void MaemoDebugRunControl::debuggingFinished()
#ifdef USE_GDBSERVER
AbstractMaemoRunControl::stopRunning(true);
#else
- AbstractMaemoRunControl::handleRunThreadFinished();
+ emit finished();
#endif
}
+void MaemoDebugRunControl::handleRemoteProcessStarted()
+{
+ startDebugging();
+}
+
+void MaemoDebugRunControl::debuggerOutput(const QString &output)
+{
+ emit appendMessage(this, QLatin1String("[gdb says:] ") + output, true);
+}
+
QString MaemoDebugRunControl::gdbServerPort() const
{
return m_devConfig.type == MaemoDeviceConfig::Physical
@@ -542,5 +659,14 @@ QString MaemoDebugRunControl::gdbServerPort() const
// but we will make sure we use the right port from the information file.
}
+QList<MaemoDeployable> MaemoDebugRunControl::filesToDeploy()
+{
+ QList<MaemoDeployable> deployables
+ = AbstractMaemoRunControl::filesToDeploy();
+ const MaemoDeployable d(m_runConfig->dumperLib(), uploadDir());
+ addDeployableIfNeeded(deployables, d);
+ return deployables;
+}
+
} // namespace Internal
} // namespace Qt4ProjectManager
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h
index ec6849b325..a5ede3fde2 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h
@@ -37,11 +37,12 @@
#include "maemodeviceconfigurations.h"
#include "maemodeployable.h"
-#include "maemosshthread.h"
+#include <coreplugin/ssh/sftpdefs.h>
#include <projectexplorer/runconfiguration.h>
#include <QtCore/QFutureInterface>
+#include <QtCore/QMap>
#include <QtCore/QScopedPointer>
#include <QtCore/QString>
@@ -49,6 +50,12 @@ QT_BEGIN_NAMESPACE
class QProcess;
QT_END_NAMESPACE
+namespace Core {
+ class SftpChannel;
+ class SshConnection;
+ class SshRemoteProcess;
+}
+
namespace Debugger {
class DebuggerRunControl;
class DebuggerStartParameters;
@@ -56,8 +63,6 @@ namespace Debugger {
namespace Qt4ProjectManager {
namespace Internal {
-class MaemoSshDeployer;
-class MaemoSshRunner;
class MaemoRunConfiguration;
class AbstractMaemoRunControl : public ProjectExplorer::RunControl
@@ -73,8 +78,7 @@ protected:
virtual void start();
virtual void stop();
- void startDeployment(bool forDebugging);
- void deploy();
+ void startDeployment();
void stopRunning(bool forDebugging);
virtual void startExecution();
void handleError(const QString &errString);
@@ -86,46 +90,55 @@ protected:
QString packageFileName() const;
QString packageFilePath() const;
QString executableFilePathOnTarget() const;
-
-protected slots:
- void handleRunThreadFinished();
+ virtual QList<MaemoDeployable> filesToDeploy();
+ bool addDeployableIfNeeded(QList<MaemoDeployable> &deployables,
+ const MaemoDeployable &deployable);
private slots:
- virtual void handleRemoteOutput(const QString &output)=0;
- void handleInitialCleanupFinished();
- void handleDeployThreadFinished();
- void handleFileCopied();
+ void handleConnected();
+ void handleConnectionFailure();
+ void handleInitialCleanupFinished(int exitStatus);
+ void handleSftpChannelInitialized();
+ void handleSftpChannelInitializationFailed(const QString &error);
+ void handleSftpJobFinished(Core::SftpJobId job, const QString &error);
+ void handleLinkProcessFinished(int exitStatus);
+ void handleInstallationFinished(int exitStatus);
+ virtual void handleRemoteProcessStarted() {}
+ void handleRemoteProcessFinished(int exitStatus);
+ void handleRemoteOutput(const QByteArray &output);
+ void handleRemoteErrorOutput(const QByteArray &output);
protected:
MaemoRunConfiguration *m_runConfig; // TODO this pointer can be invalid
const MaemoDeviceConfig m_devConfig;
private:
- bool addDeployableIfNeeded(const MaemoDeployable &deployable);
-
- virtual void startInternal()=0;
virtual void stopInternal()=0;
virtual QString remoteCall() const=0;
- void startInitialCleanup();
void killRemoteProcesses(const QStringList &apps, bool initialCleanup);
+ void cancelActions();
+ template<class SshChannel> void closeSshChannel(SshChannel &channel);
void startExecutionIfPossible();
bool isCleaning() const;
bool isDeploying() const;
QString remoteSudo() const;
- QString remoteInstallCommand() const;
QString uploadFilePath(const MaemoDeployable &deployable) const;
+ void handleDeploymentFinished();
QFutureInterface<void> m_progress;
- QScopedPointer<MaemoSshDeployer> m_sshDeployer;
- QScopedPointer<MaemoSshRunner> m_sshRunner;
- QScopedPointer<MaemoSshRunner> m_sshStopper;
- QScopedPointer<MaemoSshRunner> m_initialCleaner;
- bool m_stoppedByUser;
-
- QList<Core::SftpTransferInfo> m_deployables;
- QMap<QString, QString> m_remoteLinks;
+ QSharedPointer<Core::SshConnection> m_connection;
+ QSharedPointer<Core::SftpChannel> m_uploader;
+ QSharedPointer<Core::SshRemoteProcess> m_installer;
+ QSharedPointer<Core::SshRemoteProcess> m_runner;
+ QSharedPointer<Core::SshRemoteProcess> m_stopper;
+ QSharedPointer<Core::SshRemoteProcess> m_initialCleaner;
+
+ typedef QPair<MaemoDeployable, QString> DeployInfo;
+ QMap<Core::SftpJobId, DeployInfo> m_uploadsInProgress;
+ QMap<QSharedPointer<Core::SshRemoteProcess>, MaemoDeployable> m_linksInProgress;
bool m_needsInstall;
+ bool m_stopped;
};
class MaemoRunControl : public AbstractMaemoRunControl
@@ -135,11 +148,7 @@ public:
explicit MaemoRunControl(ProjectExplorer::RunConfiguration *runConfiguration);
~MaemoRunControl();
-private slots:
- virtual void handleRemoteOutput(const QString &output);
-
private:
- virtual void startInternal();
virtual void stopInternal();
virtual QString remoteCall() const;
};
@@ -153,22 +162,21 @@ public:
bool isRunning() const;
private slots:
- virtual void handleRemoteOutput(const QString &output);
+ virtual void handleRemoteProcessStarted();
+ void debuggerOutput(const QString &output);
void debuggingFinished();
private:
- virtual void startInternal();
virtual void stopInternal();
virtual void startExecution();
virtual QString remoteCall() const;
+ virtual QList<MaemoDeployable> filesToDeploy();
QString gdbServerPort() const;
void startDebugging();
Debugger::DebuggerRunControl *m_debuggerRunControl;
QSharedPointer<Debugger::DebuggerStartParameters> m_startParams;
-
- bool m_debuggingStarted;
};
} // namespace Internal
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.cpp
index 6ef22cf3d4..0d2e3a7fbe 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.cpp
@@ -39,7 +39,9 @@
#include "maemoconfigtestdialog.h"
#include "maemodeviceconfigurations.h"
#include "maemosshconfigdialog.h"
-#include "maemosshthread.h"
+
+#include <coreplugin/ssh/sshconnection.h>
+#include <coreplugin/ssh/sshremoteprocess.h>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
@@ -52,6 +54,8 @@
#include <algorithm>
+using namespace Core;
+
namespace Qt4ProjectManager {
namespace Internal {
@@ -97,8 +101,7 @@ MaemoSettingsWidget::MaemoSettingsWidget(QWidget *parent)
: QWidget(parent),
m_ui(new Ui_MaemoSettingsWidget),
m_devConfs(MaemoDeviceConfigurations::instance().devConfigs()),
- m_nameValidator(new NameValidator(m_devConfs)),
- m_keyDeployer(0)
+ m_nameValidator(new NameValidator(m_devConfs))
{
initGui();
}
@@ -195,7 +198,7 @@ void MaemoSettingsWidget::display(const MaemoDeviceConfig &devConfig)
otherConfig->server.pwd = devConfig.server.pwd;
otherConfig->server.privateKeyFile = devConfig.server.privateKeyFile;
- if (devConfig.server.authType == Core::SshServerInfo::AuthByPwd)
+ if (devConfig.server.authType == Core::SshConnectionParameters::AuthByPwd)
m_ui->passwordButton->setChecked(true);
else
m_ui->keyButton->setChecked(true);
@@ -272,7 +275,7 @@ void MaemoSettingsWidget::authenticationTypeChanged()
{
const bool usePassword = m_ui->passwordButton->isChecked();
currentConfig().server.authType
- = usePassword ? Core::SshServerInfo::AuthByPwd : Core::SshServerInfo::AuthByKey;
+ = usePassword ? Core::SshConnectionParameters::AuthByPwd : Core::SshConnectionParameters::AuthByKey;
m_ui->pwdLineEdit->setEnabled(usePassword);
m_ui->passwordLabel->setEnabled(usePassword);
m_ui->keyFileLineEdit->setEnabled(!usePassword);
@@ -337,13 +340,32 @@ void MaemoSettingsWidget::deployKey()
if (m_keyDeployer)
return;
+ disconnect(m_ui->deployKeyButton, 0, this, 0);
+ m_ui->deployKeyButton->setText(tr("Stop Deploying"));
+ connect(m_ui->deployKeyButton, SIGNAL(clicked()), this,
+ SLOT(stopDeploying()));
+ m_connection = SshConnection::create();
+ connect(m_connection.data(), SIGNAL(connected()), this,
+ SLOT(handleConnected()));
+ connect(m_connection.data(), SIGNAL(error(SshError)), this,
+ SLOT(handleConnectionFailure()));
+ m_connection->connectToHost(currentConfig().server);
+}
+
+void MaemoSettingsWidget::handleConnected()
+{
+ if (!m_connection)
+ return;
+
const QString &dir
= QFileInfo(currentConfig().server.privateKeyFile).path();
QString publicKeyFileName = QFileDialog::getOpenFileName(this,
tr("Choose Public Key File"), dir,
tr("Public Key Files(*.pub);;All Files (*)"));
- if (publicKeyFileName.isEmpty())
+ if (publicKeyFileName.isEmpty()) {
+ stopDeploying();
return;
+ }
QFile keyFile(publicKeyFileName);
QByteArray key;
@@ -353,32 +375,45 @@ void MaemoSettingsWidget::deployKey()
if (!keyFileAccessible || keyFile.error() != QFile::NoError) {
QMessageBox::critical(this, tr("Deployment Failed"),
tr("Could not read public key file '%1'.").arg(publicKeyFileName));
+ stopDeploying();
return;
}
- m_ui->deployKeyButton->disconnect();
- const QString command = QLatin1String("test -d .ssh "
- "|| mkdir .ssh && chmod 0700 .ssh && echo '")
- + key + QLatin1String("' >> .ssh/authorized_keys");
- m_keyDeployer = new MaemoSshRunner(currentConfig().server, command);
- connect(m_keyDeployer, SIGNAL(finished()),
- this, SLOT(handleDeployThreadFinished()));
- m_ui->deployKeyButton->setText(tr("Stop Deploying"));
- connect(m_ui->deployKeyButton, SIGNAL(clicked()), this, SLOT(stopDeploying()));
+ const QByteArray command = "test -d .ssh "
+ "|| mkdir .ssh && chmod 0700 .ssh && echo '"
+ + key + "' >> .ssh/authorized_keys";
+ m_keyDeployer = m_connection->createRemoteProcess(command);
+ connect(m_keyDeployer.data(), SIGNAL(closed(int)), this,
+ SLOT(handleKeyUploadFinished(int)));
m_keyDeployer->start();
}
-void MaemoSettingsWidget::handleDeployThreadFinished()
+void MaemoSettingsWidget::handleConnectionFailure()
{
- if (!m_keyDeployer)
+ if (!m_connection)
return;
- if (m_keyDeployer->hasError()) {
- QMessageBox::critical(this, tr("Deployment Failed"),
- tr("Key deployment failed: %1").arg(m_keyDeployer->error()));
- } else {
+ QMessageBox::critical(this, tr("Deployment Failed"),
+ tr("Could not connect to host: %1").arg(m_connection->errorString()));
+ stopDeploying();
+}
+
+void MaemoSettingsWidget::handleKeyUploadFinished(int exitStatus)
+{
+ Q_ASSERT(exitStatus == SshRemoteProcess::FailedToStart
+ || exitStatus == SshRemoteProcess::KilledBySignal
+ || exitStatus == SshRemoteProcess::ExitedNormally);
+
+ if (!m_connection)
+ return;
+
+ if (exitStatus == SshRemoteProcess::ExitedNormally
+ && m_keyDeployer->exitCode() == 0) {
QMessageBox::information(this, tr("Deployment Succeeded"),
tr("Key was successfully deployed."));
+ } else {
+ QMessageBox::critical(this, tr("Deployment Failed"),
+ tr("Key deployment failed: %1.").arg(m_keyDeployer->errorString()));
}
stopDeploying();
}
@@ -386,14 +421,14 @@ void MaemoSettingsWidget::handleDeployThreadFinished()
void MaemoSettingsWidget::stopDeploying()
{
if (m_keyDeployer) {
- m_ui->deployKeyButton->disconnect();
- m_keyDeployer->disconnect();
- m_keyDeployer->stop();
- delete m_keyDeployer;
- m_keyDeployer = 0;
- m_ui->deployKeyButton->setText(tr("Deploy Public Key ..."));
- connect(m_ui->deployKeyButton, SIGNAL(clicked()), this, SLOT(deployKey()));
+ disconnect(m_keyDeployer.data(), 0, this, 0);
+ m_keyDeployer = SshRemoteProcess::Ptr();
}
+ if (m_connection)
+ disconnect(m_connection.data(), 0, this, 0);
+ m_ui->deployKeyButton->disconnect();
+ m_ui->deployKeyButton->setText(tr("Deploy Public Key ..."));
+ connect(m_ui->deployKeyButton, SIGNAL(clicked()), this, SLOT(deployKey()));
}
void MaemoSettingsWidget::currentConfigChanged(int index)
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.h b/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.h
index e8c676696a..cc17e2262c 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.h
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.h
@@ -38,6 +38,7 @@
#include "maemodeviceconfigurations.h"
#include <QtCore/QList>
+#include <QtCore/QSharedPointer>
#include <QtCore/QString>
#include <QtGui/QWidget>
@@ -47,6 +48,11 @@ class QLineEdit;
class Ui_MaemoSettingsWidget;
QT_END_NAMESPACE
+namespace Core {
+class SshConnection;
+class SshRemoteProcess;
+}
+
namespace Qt4ProjectManager {
namespace Internal {
@@ -86,8 +92,10 @@ private slots:
// For key deploying.
void deployKey();
- void handleDeployThreadFinished();
void stopDeploying();
+ void handleConnected();
+ void handleConnectionFailure();
+ void handleKeyUploadFinished(int exitStatus);
private:
void initGui();
@@ -102,7 +110,8 @@ private:
MaemoDeviceConfig m_lastConfigHW;
MaemoDeviceConfig m_lastConfigSim;
NameValidator * const m_nameValidator;
- MaemoSshRunner *m_keyDeployer;
+ QSharedPointer<Core::SshConnection> m_connection;
+ QSharedPointer<Core::SshRemoteProcess> m_keyDeployer;
};
} // namespace Internal
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosshconfigdialog.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemosshconfigdialog.cpp
index 6052b702a2..48d48fb0f9 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemosshconfigdialog.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemosshconfigdialog.cpp
@@ -45,13 +45,13 @@
#include <QtGui/QMessageBox>
#include <QtNetwork/QHostInfo>
-
+using namespace Core;
using namespace Qt4ProjectManager::Internal;
MaemoSshConfigDialog::MaemoSshConfigDialog(QWidget *parent)
: QDialog(parent)
, home(QDesktopServices::storageLocation(QDesktopServices::HomeLocation))
- , m_keyGenerator(new Core::SshKeyGenerator)
+ , m_keyGenerator(new SshKeyGenerator)
{
m_ui.setupUi(this);
@@ -75,16 +75,16 @@ void MaemoSshConfigDialog::slotToggled()
void MaemoSshConfigDialog::generateSshKey()
{
- const Core::SshKeyGenerator::KeyType keyType = m_ui.rsa->isChecked()
- ? Core::SshKeyGenerator::Rsa
- : Core::SshKeyGenerator::Dsa;
+ const SshKeyGenerator::KeyType keyType = m_ui.rsa->isChecked()
+ ? SshKeyGenerator::Rsa
+ : SshKeyGenerator::Dsa;
QByteArray userId = QString(home.mid(home.lastIndexOf(QLatin1Char('/')) + 1)
+ QLatin1Char('@') + QHostInfo::localHostName()).toUtf8();
QApplication::setOverrideCursor(Qt::BusyCursor);
- if (m_keyGenerator->generateKeys(keyType, userId,
+ if (m_keyGenerator->generateKeys(keyType, SshKeyGenerator::OpenSsl,
m_ui.comboBox->currentText().toUShort())) {
m_ui.plainTextEdit->setPlainText(m_keyGenerator->publicKey());
m_ui.savePublicKey->setEnabled(true);
@@ -117,7 +117,7 @@ void MaemoSshConfigDialog::saveKey(bool publicKey)
{
checkSshDir();
const QString suggestedTypeSuffix =
- m_keyGenerator->type() == Core::SshKeyGenerator::Rsa ? "rsa" : "dsa";
+ m_keyGenerator->type() == SshKeyGenerator::Rsa ? "rsa" : "dsa";
const QString suggestedName = home + QString::fromLatin1("/.ssh/id_%1%2")
.arg(suggestedTypeSuffix).arg(publicKey ? ".pub" : "");
const QString dlgTitle
@@ -131,8 +131,8 @@ void MaemoSshConfigDialog::saveKey(bool publicKey)
const bool canOpen = file.open(QIODevice::WriteOnly);
if (canOpen)
file.write(publicKey
- ? m_keyGenerator->publicKey().toUtf8()
- : m_keyGenerator->privateKey().toUtf8());
+ ? m_keyGenerator->publicKey()
+ : m_keyGenerator->privateKey());
if (!canOpen || file.error() != QFile::NoError) {
QMessageBox::critical(this, tr("Error writing file"),
tr("Could not write file '%1':\n %2")
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.cpp
deleted file mode 100644
index a31c06dec4..0000000000
--- a/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.cpp
+++ /dev/null
@@ -1,215 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
-**
-** This file is part of the Qt Creator.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** No Commercial Usage
-** This file contains pre-release code and may not be distributed.
-** You may use this file in accordance with the terms and conditions
-** contained in the Technology Preview License Agreement accompanying
-** this package.
-**
-** 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 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.
-**
-** If you have questions regarding the use of this file, please contact
-** Nokia at qt-info@nokia.com.
-**
-**
-**
-**
-**
-**
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "maemosshthread.h"
-
-namespace Qt4ProjectManager {
-namespace Internal {
-
-template <class SshConnection> MaemoSshThread<SshConnection>::MaemoSshThread(const Core::SshServerInfo &server)
- : m_server(server), m_stopRequested(false)
-{
-}
-
-template <class SshConnection> MaemoSshThread<SshConnection>::~MaemoSshThread()
-{
- stop();
- wait();
-}
-
-template <class SshConnection> void MaemoSshThread<SshConnection>::run()
-{
- if (m_stopRequested)
- return;
-
- if (!runInternal())
- m_error = m_connection->error();
-}
-
-template<class SshConnection> void MaemoSshThread<SshConnection>::stop()
-{
- m_mutex.lock();
- m_stopRequested = true;
- m_waitCond.wakeAll();
- const bool hasConnection = !m_connection.isNull();
- if (hasConnection)
- m_connection->quit();
- m_mutex.unlock();
-}
-
-template<class SshConnection> void MaemoSshThread<SshConnection>::waitForStop()
-{
- m_mutex.lock();
- while (!stopRequested())
- m_waitCond.wait(&m_mutex);
- m_mutex.unlock();
-}
-
-template<class SshConnection> void MaemoSshThread<SshConnection>::createConnection()
-{
- typename SshConnection::Ptr connection = SshConnection::create(m_server);
- m_mutex.lock();
- m_connection = connection;
- m_mutex.unlock();
-}
-
-MaemoSshRunner::MaemoSshRunner(const Core::SshServerInfo &server,
- const QString &command)
- : MaemoSshThread<Core::InteractiveSshConnection>(server),
- m_command(command)
-{
- m_prompt = server.uname == QLatin1String("root") ? "#" : "$";
-}
-
-bool MaemoSshRunner::runInternal()
-{
- createConnection();
- connect(m_connection.data(), SIGNAL(remoteOutputAvailable()),
- this, SLOT(handleRemoteOutput()));
- initState();
- if (!m_connection->start())
- return false;
- if (stopRequested())
- return true;
-
- waitForStop();
- return !m_connection->hasError();
-}
-
-void MaemoSshRunner::initState()
-{
- m_endMarkerCount = 0;
- m_promptEncountered = false;
- m_potentialEndMarkerPrefix.clear();
-}
-
-void MaemoSshRunner::handleRemoteOutput()
-{
- const QByteArray output
- = m_potentialEndMarkerPrefix + m_connection->waitForRemoteOutput(0);
-
- // Wait for a prompt before sending the command.
- if (!m_promptEncountered) {
- if (output.indexOf(m_prompt) != -1) {
- m_promptEncountered = true;
-
- /*
- * We don't have access to the remote process management, so we
- * try to track the lifetime of the process by adding a second command
- * that prints a rare character. When it occurs for the second time (the
- * first one is the echo from the remote terminal), we assume the
- * process has finished. If anyone actually prints this special character
- * in their application, they are out of luck.
- */
- const QString finalCommand = m_command + QLatin1String(";echo ")
- + QString::fromUtf8(EndMarker) + QLatin1Char('\n');
- if (!m_connection->sendInput(finalCommand.toUtf8()))
- stop();
- }
- return;
- }
-
- /*
- * The output the user should see is everything after the first
- * and before the last occurrence of our marker string.
- */
- int firstCharToEmit;
- int charsToEmitCount;
- const int endMarkerPos = output.indexOf(EndMarker);
- if (endMarkerPos != -1) {
- if (m_endMarkerCount++ == 0) {
- firstCharToEmit = endMarkerPos + EndMarker.count() + 1;
- int endMarkerPos2
- = output.indexOf(EndMarker, firstCharToEmit);
- if (endMarkerPos2 != -1) {
- ++ m_endMarkerCount;
- charsToEmitCount = endMarkerPos2 - firstCharToEmit;
- } else {
- charsToEmitCount = -1;
- }
- } else {
- firstCharToEmit = m_potentialEndMarkerPrefix.count();
- charsToEmitCount = endMarkerPos - firstCharToEmit;
- }
- } else {
- if (m_endMarkerCount == 0) {
- charsToEmitCount = 0;
- } else {
- firstCharToEmit = m_potentialEndMarkerPrefix.count();
- charsToEmitCount = -1;
- }
- }
-
- if (charsToEmitCount != 0) {
- emit remoteOutput(QString::fromUtf8(output.data() + firstCharToEmit,
- charsToEmitCount));
- }
- if (m_endMarkerCount == 2)
- stop();
-
- m_potentialEndMarkerPrefix = output.right(EndMarker.count() - 1);
-}
-
-const QByteArray MaemoSshRunner::EndMarker(QString(QChar(0x13a0)).toUtf8());
-
-
-MaemoSshDeployer::MaemoSshDeployer(const Core::SshServerInfo &server,
- const QList<Core::SftpTransferInfo> &deploySpecs)
- : MaemoSshThread<Core::SftpConnection>(server),
- m_deploySpecs(deploySpecs)
-{
-}
-
-bool MaemoSshDeployer::runInternal()
-{
- createConnection();
- if (!m_connection->start())
- return false;
- if (stopRequested())
- return true;
-
- connect(m_connection.data(), SIGNAL(fileCopied(QString)),
- this, SIGNAL(fileCopied(QString)));
- return m_connection->transferFiles(m_deploySpecs);
-}
-
-} // namespace Internal
-} // namespace Qt4ProjectManager
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.h b/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.h
deleted file mode 100644
index 79107d0f60..0000000000
--- a/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.h
+++ /dev/null
@@ -1,132 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
-**
-** This file is part of the Qt Creator.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** No Commercial Usage
-** This file contains pre-release code and may not be distributed.
-** You may use this file in accordance with the terms and conditions
-** contained in the Technology Preview License Agreement accompanying
-** this package.
-**
-** 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 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.
-**
-** If you have questions regarding the use of this file, please contact
-** Nokia at qt-info@nokia.com.
-**
-**
-**
-**
-**
-**
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef MAEMOSSHTHREAD_H
-#define MAEMOSSHTHREAD_H
-
-#include "maemodeviceconfigurations.h"
-
-#include <coreplugin/ssh/sshconnection.h>
-
-#include <QtCore/QByteArray>
-#include <QtCore/QList>
-#include <QtCore/QMutex>
-#include <QtCore/QThread>
-#include <QtCore/QWaitCondition>
-
-namespace Qt4ProjectManager {
-namespace Internal {
-
-template<class SshConnection> class MaemoSshThread : public QThread
-{
- Q_DISABLE_COPY(MaemoSshThread)
-public:
- QString error() const { return m_error; }
- bool hasError() const { return !m_error.isEmpty(); }
- void stop();
- virtual void run();
- ~MaemoSshThread();
-
-protected:
- MaemoSshThread(const Core::SshServerInfo &server);
- void createConnection();
- bool stopRequested() const { return m_stopRequested; }
- void waitForStop();
-
- typename SshConnection::Ptr m_connection;
-
-private:
- virtual bool runInternal() = 0;
-
- const Core::SshServerInfo m_server;
- bool m_stopRequested;
- QString m_error;
- QMutex m_mutex;
- QWaitCondition m_waitCond;
-};
-
-
-class MaemoSshRunner : public MaemoSshThread<Core::InteractiveSshConnection>
-{
- Q_OBJECT
- Q_DISABLE_COPY(MaemoSshRunner)
-public:
- MaemoSshRunner(const Core::SshServerInfo &server, const QString &command);
-
-signals:
- void remoteOutput(const QString &output);
-
-private:
- virtual bool runInternal();
- Q_SLOT void handleRemoteOutput();
- void initState();
-
- static const QByteArray EndMarker;
-
- const QString m_command;
- const char *m_prompt;
- int m_endMarkerCount;
- bool m_promptEncountered;
- QByteArray m_potentialEndMarkerPrefix;
-};
-
-
-class MaemoSshDeployer : public MaemoSshThread<Core::SftpConnection>
-{
- Q_OBJECT
- Q_DISABLE_COPY(MaemoSshDeployer)
-public:
- MaemoSshDeployer(const Core::SshServerInfo &server,
- const QList<Core::SftpTransferInfo> &deploySpecs);
-
-signals:
- void fileCopied(const QString &filePath);
-
-private:
- virtual bool runInternal();
-
- const QList<Core::SftpTransferInfo> m_deploySpecs;
-};
-
-} // namespace Internal
-} // namespace Qt4ProjectManager
-
-#endif // MAEMOSSHTHREAD_H
diff --git a/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri b/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri
index 346c1b8a06..30633dd68d 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri
+++ b/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri
@@ -10,7 +10,6 @@ HEADERS += \
$$PWD/maemosettingspage.h \
$$PWD/maemosettingswidget.h \
$$PWD/maemosshconfigdialog.h \
- $$PWD/maemosshthread.h \
$$PWD/maemotoolchain.h \
$$PWD/maemopackagecreationstep.h \
$$PWD/maemopackagecreationfactory.h \
@@ -33,7 +32,6 @@ SOURCES += \
$$PWD/maemosettingspage.cpp \
$$PWD/maemosettingswidget.cpp \
$$PWD/maemosshconfigdialog.cpp \
- $$PWD/maemosshthread.cpp \
$$PWD/maemotoolchain.cpp \
$$PWD/maemopackagecreationstep.cpp \
$$PWD/maemopackagecreationfactory.cpp \