diff options
author | ck <qt-info@nokia.com> | 2010-07-12 09:33:22 +0200 |
---|---|---|
committer | ck <qt-info@nokia.com> | 2010-07-12 09:33:22 +0200 |
commit | 65eb1d551592421f5e0e961c8c1dd2d056023697 (patch) | |
tree | e4698e3ee36443d67911ff0de3b9637aa0917484 /src/plugins | |
parent | e43287fb3a4e2b69ada07455760aa30afb7eb10c (diff) | |
download | qt-creator-65eb1d551592421f5e0e961c8c1dd2d056023697.tar.gz |
Replace SSH library.
Diffstat (limited to 'src/plugins')
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 \ |