summaryrefslogtreecommitdiff
path: root/src/libs/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/ssh')
-rw-r--r--src/libs/ssh/sftpchannel.cpp980
-rw-r--r--src/libs/ssh/sftpchannel.h113
-rw-r--r--src/libs/ssh/sftpchannel_p.h135
-rw-r--r--src/libs/ssh/sftpdefs.cpp35
-rw-r--r--src/libs/ssh/sftpdefs.h69
-rw-r--r--src/libs/ssh/sftpfilesystemmodel.cpp386
-rw-r--r--src/libs/ssh/sftpfilesystemmodel.h110
-rw-r--r--src/libs/ssh/sftpincomingpacket.cpp225
-rw-r--r--src/libs/ssh/sftpincomingpacket_p.h114
-rw-r--r--src/libs/ssh/sftpoperation.cpp227
-rw-r--r--src/libs/ssh/sftpoperation_p.h254
-rw-r--r--src/libs/ssh/sftpoutgoingpacket.cpp229
-rw-r--r--src/libs/ssh/sftpoutgoingpacket_p.h94
-rw-r--r--src/libs/ssh/sftppacket.cpp56
-rw-r--r--src/libs/ssh/sftppacket_p.h119
-rw-r--r--src/libs/ssh/ssh.pri2
-rw-r--r--src/libs/ssh/ssh.pro67
-rw-r--r--src/libs/ssh/ssh.qbs51
-rw-r--r--src/libs/ssh/ssh_dependencies.pri1
-rw-r--r--src/libs/ssh/ssh_global.h49
-rw-r--r--src/libs/ssh/sshbotanconversions_p.h102
-rw-r--r--src/libs/ssh/sshcapabilities.cpp106
-rw-r--r--src/libs/ssh/sshcapabilities_p.h75
-rw-r--r--src/libs/ssh/sshchannel.cpp263
-rw-r--r--src/libs/ssh/sshchannel_p.h124
-rw-r--r--src/libs/ssh/sshchannelmanager.cpp198
-rw-r--r--src/libs/ssh/sshchannelmanager_p.h97
-rw-r--r--src/libs/ssh/sshconnection.cpp728
-rw-r--r--src/libs/ssh/sshconnection.h124
-rw-r--r--src/libs/ssh/sshconnection_p.h177
-rw-r--r--src/libs/ssh/sshconnectionmanager.cpp227
-rw-r--r--src/libs/ssh/sshconnectionmanager.h68
-rw-r--r--src/libs/ssh/sshcryptofacility.cpp387
-rw-r--r--src/libs/ssh/sshcryptofacility_p.h157
-rw-r--r--src/libs/ssh/ssherrors.h46
-rw-r--r--src/libs/ssh/sshexception_p.h92
-rw-r--r--src/libs/ssh/sshincomingpacket.cpp463
-rw-r--r--src/libs/ssh/sshincomingpacket_p.h195
-rw-r--r--src/libs/ssh/sshkeycreationdialog.cpp145
-rw-r--r--src/libs/ssh/sshkeycreationdialog.h70
-rw-r--r--src/libs/ssh/sshkeycreationdialog.ui272
-rw-r--r--src/libs/ssh/sshkeyexchange.cpp225
-rw-r--r--src/libs/ssh/sshkeyexchange_p.h90
-rw-r--r--src/libs/ssh/sshkeygenerator.cpp201
-rw-r--r--src/libs/ssh/sshkeygenerator.h85
-rw-r--r--src/libs/ssh/sshkeypasswordretriever.cpp65
-rw-r--r--src/libs/ssh/sshkeypasswordretriever_p.h52
-rw-r--r--src/libs/ssh/sshoutgoingpacket.cpp325
-rw-r--r--src/libs/ssh/sshoutgoingpacket_p.h108
-rw-r--r--src/libs/ssh/sshpacket.cpp168
-rw-r--r--src/libs/ssh/sshpacket_p.h146
-rw-r--r--src/libs/ssh/sshpacketparser.cpp156
-rw-r--r--src/libs/ssh/sshpacketparser_p.h84
-rw-r--r--src/libs/ssh/sshpseudoterminal.h120
-rw-r--r--src/libs/ssh/sshremoteprocess.cpp384
-rw-r--r--src/libs/ssh/sshremoteprocess.h130
-rw-r--r--src/libs/ssh/sshremoteprocess_p.h114
-rw-r--r--src/libs/ssh/sshremoteprocessrunner.cpp269
-rw-r--r--src/libs/ssh/sshremoteprocessrunner.h94
-rw-r--r--src/libs/ssh/sshsendfacility.cpp222
-rw-r--r--src/libs/ssh/sshsendfacility_p.h101
61 files changed, 10571 insertions, 0 deletions
diff --git a/src/libs/ssh/sftpchannel.cpp b/src/libs/ssh/sftpchannel.cpp
new file mode 100644
index 0000000000..c4a2181868
--- /dev/null
+++ b/src/libs/ssh/sftpchannel.cpp
@@ -0,0 +1,980 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sftpchannel.h"
+#include "sftpchannel_p.h"
+
+#include "sshexception_p.h"
+#include "sshincomingpacket_p.h"
+#include "sshsendfacility_p.h"
+
+#include <QDir>
+#include <QFile>
+
+/*!
+ \class QSsh::SftpChannel
+
+ \brief This class provides SFTP operations.
+
+ Objects are created via SshConnection::createSftpChannel().
+ The channel needs to be initialized with
+ a call to initialize() and is closed via closeChannel(). After closing
+ a channel, no more operations are possible. It cannot be re-opened
+ using initialize(); use SshConnection::createSftpChannel() if you need
+ a new one.
+
+ After the initialized() signal has been emitted, operations can be started.
+ All SFTP operations are asynchronous (non-blocking) and can be in-flight
+ simultaneously (though callers must ensure that concurrently running jobs
+ are independent of each other, e.g. they must not write to the same file).
+ Operations are identified by their job id, which is returned by
+ the respective member function. If the function can right away detect that
+ the operation cannot succeed, it returns SftpInvalidJob. If an error occurs
+ later, the finished() signal is emitted for the respective job with a
+ non-empty error string.
+
+ Note that directory names must not have a trailing slash.
+*/
+
+namespace QSsh {
+namespace Internal {
+namespace {
+ const quint32 ProtocolVersion = 3;
+
+ QString errorMessage(const QString &serverMessage,
+ const QString &alternativeMessage)
+ {
+ return serverMessage.isEmpty() ? alternativeMessage : serverMessage;
+ }
+
+ QString errorMessage(const SftpStatusResponse &response,
+ const QString &alternativeMessage)
+ {
+ return response.status == SSH_FX_OK ? QString()
+ : errorMessage(response.errorString, alternativeMessage);
+ }
+} // anonymous namespace
+} // namespace Internal
+
+SftpChannel::SftpChannel(quint32 channelId,
+ Internal::SshSendFacility &sendFacility)
+ : d(new Internal::SftpChannelPrivate(channelId, sendFacility, this))
+{
+ connect(d, SIGNAL(initialized()), this, SIGNAL(initialized()),
+ Qt::QueuedConnection);
+ connect(d, SIGNAL(initializationFailed(QString)), this,
+ SIGNAL(initializationFailed(QString)), Qt::QueuedConnection);
+ connect(d, SIGNAL(dataAvailable(QSsh::SftpJobId,QString)), this,
+ SIGNAL(dataAvailable(QSsh::SftpJobId,QString)), Qt::QueuedConnection);
+ connect(d, SIGNAL(fileInfoAvailable(QSsh::SftpJobId,QList<QSsh::SftpFileInfo>)), this,
+ SIGNAL(fileInfoAvailable(QSsh::SftpJobId,QList<QSsh::SftpFileInfo>)),
+ Qt::QueuedConnection);
+ connect(d, SIGNAL(finished(QSsh::SftpJobId,QString)), this,
+ SIGNAL(finished(QSsh::SftpJobId,QString)), Qt::QueuedConnection);
+ connect(d, SIGNAL(closed()), this, SIGNAL(closed()), Qt::QueuedConnection);
+}
+
+SftpChannel::State SftpChannel::state() const
+{
+ switch (d->channelState()) {
+ case Internal::AbstractSshChannel::Inactive:
+ return Uninitialized;
+ case Internal::AbstractSshChannel::SessionRequested:
+ return Initializing;
+ case Internal::AbstractSshChannel::CloseRequested:
+ return Closing;
+ case Internal::AbstractSshChannel::Closed:
+ return Closed;
+ case Internal::AbstractSshChannel::SessionEstablished:
+ return d->m_sftpState == Internal::SftpChannelPrivate::Initialized
+ ? Initialized : Initializing;
+ default:
+ Q_ASSERT(!"Oh no, we forgot to handle a channel state!");
+ return Closed; // For the compiler.
+ }
+}
+
+void SftpChannel::initialize()
+{
+ d->requestSessionStart();
+ d->m_sftpState = Internal::SftpChannelPrivate::SubsystemRequested;
+}
+
+void SftpChannel::closeChannel()
+{
+ d->closeChannel();
+}
+
+SftpJobId SftpChannel::statFile(const QString &path)
+{
+ return d->createJob(Internal::SftpStatFile::Ptr(
+ new Internal::SftpStatFile(++d->m_nextJobId, path)));
+}
+
+SftpJobId SftpChannel::listDirectory(const QString &path)
+{
+ return d->createJob(Internal::SftpListDir::Ptr(
+ new Internal::SftpListDir(++d->m_nextJobId, path)));
+}
+
+SftpJobId SftpChannel::createDirectory(const QString &path)
+{
+ return d->createJob(Internal::SftpMakeDir::Ptr(
+ new Internal::SftpMakeDir(++d->m_nextJobId, path)));
+}
+
+SftpJobId SftpChannel::removeDirectory(const QString &path)
+{
+ return d->createJob(Internal::SftpRmDir::Ptr(
+ new Internal::SftpRmDir(++d->m_nextJobId, path)));
+}
+
+SftpJobId SftpChannel::removeFile(const QString &path)
+{
+ return d->createJob(Internal::SftpRm::Ptr(
+ new Internal::SftpRm(++d->m_nextJobId, path)));
+}
+
+SftpJobId SftpChannel::renameFileOrDirectory(const QString &oldPath,
+ const QString &newPath)
+{
+ return d->createJob(Internal::SftpRename::Ptr(
+ new Internal::SftpRename(++d->m_nextJobId, oldPath, newPath)));
+}
+
+SftpJobId SftpChannel::createLink(const QString &filePath, const QString &target)
+{
+ return d->createJob(Internal::SftpCreateLink::Ptr(
+ new Internal::SftpCreateLink(++d->m_nextJobId, filePath, target)));
+}
+
+SftpJobId SftpChannel::createFile(const QString &path, SftpOverwriteMode mode)
+{
+ return d->createJob(Internal::SftpCreateFile::Ptr(
+ new Internal::SftpCreateFile(++d->m_nextJobId, path, mode)));
+}
+
+SftpJobId SftpChannel::uploadFile(const QString &localFilePath,
+ const QString &remoteFilePath, SftpOverwriteMode mode)
+{
+ QSharedPointer<QFile> localFile(new QFile(localFilePath));
+ if (!localFile->open(QIODevice::ReadOnly))
+ return SftpInvalidJob;
+ return d->createJob(Internal::SftpUploadFile::Ptr(
+ new Internal::SftpUploadFile(++d->m_nextJobId, remoteFilePath, localFile, mode)));
+}
+
+SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath,
+ const QString &localFilePath, SftpOverwriteMode mode)
+{
+ QSharedPointer<QFile> localFile(new QFile(localFilePath));
+ 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)));
+}
+
+SftpJobId SftpChannel::uploadDir(const QString &localDirPath,
+ const QString &remoteParentDirPath)
+{
+ if (state() != Initialized)
+ return SftpInvalidJob;
+ const QDir localDir(localDirPath);
+ if (!localDir.exists() || !localDir.isReadable())
+ return SftpInvalidJob;
+ const Internal::SftpUploadDir::Ptr uploadDirOp(
+ new Internal::SftpUploadDir(++d->m_nextJobId));
+ const QString remoteDirPath
+ = remoteParentDirPath + QLatin1Char('/') + localDir.dirName();
+ const Internal::SftpMakeDir::Ptr mkdirOp(
+ new Internal::SftpMakeDir(++d->m_nextJobId, remoteDirPath, uploadDirOp));
+ uploadDirOp->mkdirsInProgress.insert(mkdirOp,
+ Internal::SftpUploadDir::Dir(localDirPath, remoteDirPath));
+ d->createJob(mkdirOp);
+ return uploadDirOp->jobId;
+}
+
+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()
+{
+ if (channelState() == CloseRequested)
+ return;
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("sftp subsystem initialized");
+#endif
+ sendData(m_outgoingPacket.generateInit(ProtocolVersion).rawData());
+ m_sftpState = InitSent;
+}
+
+void SftpChannelPrivate::handleChannelFailure()
+{
+ if (channelState() == CloseRequested)
+ return;
+
+ if (m_sftpState != SubsystemRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_MSG_CHANNEL_FAILURE packet.");
+ }
+ emit initializationFailed(tr("Server could not start sftp subsystem."));
+ closeChannel();
+}
+
+void SftpChannelPrivate::handleChannelDataInternal(const QByteArray &data)
+{
+ if (channelState() == CloseRequested)
+ return;
+
+ m_incomingData += data;
+ m_incomingPacket.consumeData(m_incomingData);
+ while (m_incomingPacket.isComplete()) {
+ handleCurrentPacket();
+ m_incomingPacket.clear();
+ m_incomingPacket.consumeData(m_incomingData);
+ }
+}
+
+void SftpChannelPrivate::handleChannelExtendedDataInternal(quint32 type,
+ const QByteArray &data)
+{
+ qWarning("Unexpected extended data '%s' of type %d on SFTP channel.",
+ data.data(), type);
+}
+
+void SftpChannelPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
+{
+ const char * const message = "Remote SFTP service exited with exit code %d";
+#ifdef CREATOR_SSH_DEBUG
+ qDebug(message, exitStatus.exitStatus);
+#else
+ if (exitStatus.exitStatus != 0)
+ qWarning(message, exitStatus.exitStatus);
+#endif
+}
+
+void SftpChannelPrivate::handleExitSignal(const SshChannelExitSignal &signal)
+{
+ qWarning("Remote SFTP service killed; signal was %s", signal.signal.data());
+}
+
+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.",
+ tr("Unexpected packet of type %1.").arg(m_incomingPacket.type()));
+ }
+}
+
+void SftpChannelPrivate::handleServerVersion()
+{
+ checkChannelActive();
+ if (m_sftpState != InitSent) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_FXP_VERSION packet.");
+ }
+
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("sftp init received");
+#endif
+ const quint32 serverVersion = m_incomingPacket.extractServerVersion();
+ if (serverVersion != ProtocolVersion) {
+ emit initializationFailed(tr("Protocol version mismatch: Expected %1, got %2")
+ .arg(serverVersion).arg(ProtocolVersion));
+ closeChannel();
+ } else {
+ m_sftpState = Initialized;
+ emit initialized();
+ }
+}
+
+void SftpChannelPrivate::handleHandle()
+{
+ const SftpHandleResponse &response = m_incomingPacket.asHandleResponse();
+ JobMap::Iterator it = lookupJob(response.requestId);
+ const QSharedPointer<AbstractSftpOperationWithHandle> job
+ = it.value().dynamicCast<AbstractSftpOperationWithHandle>();
+ if (job.isNull()) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_FXP_HANDLE packet.");
+ }
+ if (job->state != AbstractSftpOperationWithHandle::OpenRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_FXP_HANDLE packet.");
+ }
+ job->remoteHandle = response.handle;
+ job->state = AbstractSftpOperationWithHandle::Open;
+
+ switch (it.value()->type()) {
+ case AbstractSftpOperation::ListDir:
+ handleLsHandle(it);
+ break;
+ case AbstractSftpOperation::CreateFile:
+ handleCreateFileHandle(it);
+ break;
+ case AbstractSftpOperation::Download:
+ handleGetHandle(it);
+ break;
+ case AbstractSftpOperation::UploadFile:
+ handlePutHandle(it);
+ break;
+ default:
+ Q_ASSERT(!"Oh no, I forgot to handle an SFTP operation type!");
+ }
+}
+
+void SftpChannelPrivate::handleLsHandle(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)
+{
+ SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>();
+ if (op->parentJob && op->parentJob->hasError)
+ sendTransferCloseHandle(op, it.key());
+
+ // OpenSSH does not implement the RFC's append functionality, so we
+ // have to emulate it.
+ if (op->mode == SftpAppendToExisting) {
+ sendData(m_outgoingPacket.generateFstat(op->remoteHandle,
+ op->jobId).rawData());
+ op->statRequested = true;
+ } else {
+ spawnWriteRequests(it);
+ }
+}
+
+void SftpChannelPrivate::handleStatus()
+{
+ const SftpStatusResponse &response = m_incomingPacket.asStatusResponse();
+#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::UploadFile:
+ handlePutStatus(it, response);
+ break;
+ case AbstractSftpOperation::MakeDir:
+ handleMkdirStatus(it, response);
+ break;
+ case AbstractSftpOperation::StatFile:
+ case AbstractSftpOperation::RmDir:
+ case AbstractSftpOperation::Rm:
+ case AbstractSftpOperation::Rename:
+ case AbstractSftpOperation::CreateFile:
+ case AbstractSftpOperation::CreateLink:
+ handleStatusGeneric(it, response);
+ break;
+ }
+}
+
+void SftpChannelPrivate::handleStatusGeneric(const JobMap::Iterator &it,
+ const SftpStatusResponse &response)
+{
+ AbstractSftpOperation::Ptr op = it.value();
+ const QString error = errorMessage(response, tr("Unknown error."));
+ emit finished(op->jobId, error);
+ m_jobs.erase(it);
+}
+
+void SftpChannelPrivate::handleMkdirStatus(const JobMap::Iterator &it,
+ const SftpStatusResponse &response)
+{
+ SftpMakeDir::Ptr op = it.value().staticCast<SftpMakeDir>();
+ if (op->parentJob == SftpUploadDir::Ptr()) {
+ handleStatusGeneric(it, response);
+ return;
+ }
+ if (op->parentJob->hasError) {
+ m_jobs.erase(it);
+ return;
+ }
+
+ typedef QMap<SftpMakeDir::Ptr, SftpUploadDir::Dir>::Iterator DirIt;
+ DirIt dirIt = op->parentJob->mkdirsInProgress.find(op);
+ Q_ASSERT(dirIt != op->parentJob->mkdirsInProgress.end());
+ const QString &remoteDir = dirIt.value().remoteDir;
+ if (response.status == SSH_FX_OK) {
+ emit dataAvailable(op->parentJob->jobId,
+ tr("Created remote directory '%1'.").arg(remoteDir));
+ } else if (response.status == SSH_FX_FAILURE) {
+ emit dataAvailable(op->parentJob->jobId,
+ tr("Remote directory '%1' already exists.").arg(remoteDir));
+ } else {
+ op->parentJob->setError();
+ emit finished(op->parentJob->jobId,
+ tr("Error creating directory '%1': %2")
+ .arg(remoteDir, response.errorString));
+ m_jobs.erase(it);
+ return;
+ }
+
+ QDir localDir(dirIt.value().localDir);
+ const QFileInfoList &dirInfos
+ = localDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ foreach (const QFileInfo &dirInfo, dirInfos) {
+ const QString remoteSubDir = remoteDir + QLatin1Char('/') + dirInfo.fileName();
+ const SftpMakeDir::Ptr mkdirOp(
+ new SftpMakeDir(++m_nextJobId, remoteSubDir, op->parentJob));
+ op->parentJob->mkdirsInProgress.insert(mkdirOp,
+ SftpUploadDir::Dir(dirInfo.absoluteFilePath(), remoteSubDir));
+ createJob(mkdirOp);
+ }
+
+ const QFileInfoList &fileInfos = localDir.entryInfoList(QDir::Files);
+ foreach (const QFileInfo &fileInfo, fileInfos) {
+ QSharedPointer<QFile> localFile(new QFile(fileInfo.absoluteFilePath()));
+ if (!localFile->open(QIODevice::ReadOnly)) {
+ op->parentJob->setError();
+ emit finished(op->parentJob->jobId,
+ tr("Could not open local file '%1': %2")
+ .arg(fileInfo.absoluteFilePath(), localFile->errorString()));
+ m_jobs.erase(it);
+ return;
+ }
+
+ const QString remoteFilePath = remoteDir + QLatin1Char('/') + fileInfo.fileName();
+ SftpUploadFile::Ptr uploadFileOp(new SftpUploadFile(++m_nextJobId,
+ remoteFilePath, localFile, SftpOverwriteExisting, op->parentJob));
+ createJob(uploadFileOp);
+ op->parentJob->uploadsInProgress.append(uploadFileOp);
+ }
+
+ op->parentJob->mkdirsInProgress.erase(dirIt);
+ if (op->parentJob->mkdirsInProgress.isEmpty()
+ && op->parentJob->uploadsInProgress.isEmpty())
+ emit finished(op->parentJob->jobId);
+ 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:
+ emit finished(op->jobId, errorMessage(response.errorString,
+ tr("Remote directory could not be opened for reading.")));
+ m_jobs.erase(it);
+ break;
+ case SftpListDir::Open:
+ if (response.status != SSH_FX_EOF)
+ reportRequestError(op, errorMessage(response.errorString,
+ tr("Failed to list remote directory contents.")));
+ op->state = SftpListDir::CloseRequested;
+ sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
+ op->jobId).rawData());
+ break;
+ case SftpListDir::CloseRequested:
+ if (!op->hasError) {
+ const QString error = errorMessage(response,
+ tr("Failed to close remote directory."));
+ emit finished(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:
+ emit finished(op->jobId,
+ errorMessage(response.errorString,
+ tr("Failed to open remote file for reading.")));
+ m_jobs.erase(it);
+ break;
+ case SftpDownload::Open:
+ if (op->statRequested) {
+ reportRequestError(op, errorMessage(response.errorString,
+ tr("Failed retrieve information on the remote file ('stat' failed).")));
+ sendTransferCloseHandle(op, response.requestId);
+ } else {
+ if ((response.status != SSH_FX_EOF || response.requestId != op->eofId)
+ && !op->hasError)
+ reportRequestError(op, errorMessage(response.errorString,
+ tr("Failed to read remote file.")));
+ finishTransferRequest(it);
+ }
+ break;
+ case SftpDownload::CloseRequested:
+ Q_ASSERT(op->inFlightCount == 1);
+ if (!op->hasError) {
+ if (response.status == SSH_FX_OK)
+ emit finished(op->jobId);
+ else
+ reportRequestError(op, errorMessage(response.errorString,
+ 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)
+{
+ SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
+ switch (job->state) {
+ case SftpUploadFile::OpenRequested: {
+ bool emitError = false;
+ if (job->parentJob) {
+ if (!job->parentJob->hasError) {
+ job->parentJob->setError();
+ emitError = true;
+ }
+ } else {
+ emitError = true;
+ }
+
+ if (emitError) {
+ emit finished(job->jobId,
+ errorMessage(response.errorString,
+ tr("Failed to open remote file for writing.")));
+ }
+ m_jobs.erase(it);
+ break;
+ }
+ case SftpUploadFile::Open:
+ if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
+ job->hasError = true;
+ finishTransferRequest(it);
+ return;
+ }
+
+ if (response.status == SSH_FX_OK) {
+ sendWriteRequest(it);
+ } else {
+ if (job->parentJob)
+ job->parentJob->setError();
+ reportRequestError(job, errorMessage(response.errorString,
+ tr("Failed to write remote file.")));
+ finishTransferRequest(it);
+ }
+ break;
+ case SftpUploadFile::CloseRequested:
+ Q_ASSERT(job->inFlightCount == 1);
+ if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
+ m_jobs.erase(it);
+ return;
+ }
+
+ if (response.status == SSH_FX_OK) {
+ if (job->parentJob) {
+ job->parentJob->uploadsInProgress.removeOne(job);
+ if (job->parentJob->mkdirsInProgress.isEmpty()
+ && job->parentJob->uploadsInProgress.isEmpty())
+ emit finished(job->parentJob->jobId);
+ } else {
+ emit finished(job->jobId);
+ }
+ } else {
+ const QString error = errorMessage(response.errorString,
+ tr("Failed to close remote file."));
+ if (job->parentJob) {
+ job->parentJob->setError();
+ emit finished(job->parentJob->jobId, error);
+ } else {
+ emit finished(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.");
+ }
+
+ QList<SftpFileInfo> fileInfoList;
+ for (int i = 0; i < response.files.count(); ++i) {
+ const SftpFile &file = response.files.at(i);
+
+ SftpFileInfo fileInfo;
+ fileInfo.name = file.fileName;
+ attributesToFileInfo(file.attributes, fileInfo);
+ fileInfoList << fileInfo;
+ }
+ emit fileInfoAvailable(op->jobId, fileInfoList);
+ sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
+ op->jobId).rawData());
+ break;
+ }
+ default:
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_FXP_NAME packet.");
+ }
+}
+
+void SftpChannelPrivate::handleReadData()
+{
+ const SftpDataResponse &response = m_incomingPacket.asDataResponse();
+ JobMap::Iterator it = lookupJob(response.requestId);
+ if (it.value()->type() != AbstractSftpOperation::Download) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_FXP_DATA packet.");
+ }
+
+ SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
+ if (op->hasError) {
+ finishTransferRequest(it);
+ return;
+ }
+
+ if (!op->localFile->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);
+
+ SftpStatFile::Ptr statOp = it.value().dynamicCast<SftpStatFile>();
+ if (statOp) {
+ SftpFileInfo fileInfo;
+ fileInfo.name = QFileInfo(statOp->path).fileName();
+ attributesToFileInfo(response.attrs, fileInfo);
+ emit fileInfoAvailable(it.key(), QList<SftpFileInfo>() << fileInfo);
+ emit finished(it.key());
+ m_jobs.erase(it);
+ return;
+ }
+
+ AbstractSftpTransfer::Ptr transfer
+ = it.value().dynamicCast<AbstractSftpTransfer>();
+ if (!transfer || transfer->state != AbstractSftpTransfer::Open
+ || !transfer->statRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_FXP_ATTRS packet.");
+ }
+ Q_ASSERT(transfer->type() == AbstractSftpOperation::UploadFile
+ || transfer->type() == AbstractSftpOperation::Download);
+
+ if (transfer->type() == AbstractSftpOperation::Download) {
+ SftpDownload::Ptr op = transfer.staticCast<SftpDownload>();
+ if (response.attrs.sizePresent) {
+ op->fileSize = response.attrs.size;
+ } else {
+ op->fileSize = 0;
+ op->eofId = op->jobId;
+ }
+ op->statRequested = false;
+ spawnReadRequests(op);
+ } else {
+ SftpUploadFile::Ptr op = transfer.staticCast<SftpUploadFile>();
+ if (op->parentJob && op->parentJob->hasError) {
+ op->hasError = true;
+ sendTransferCloseHandle(op, op->jobId);
+ return;
+ }
+
+ if (response.attrs.sizePresent) {
+ op->offset = response.attrs.size;
+ spawnWriteRequests(it);
+ } else {
+ if (op->parentJob)
+ op->parentJob->setError();
+ reportRequestError(op, tr("Cannot append to remote file: "
+ "Server does not support the 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()
+{
+ m_jobs.clear();
+ m_incomingData.clear();
+ m_incomingPacket.clear();
+ emit closed();
+}
+
+void SftpChannelPrivate::handleOpenSuccessInternal()
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("SFTP session started");
+#endif
+ m_sendFacility.sendSftpPacket(remoteChannel());
+ m_sftpState = SubsystemRequested;
+}
+
+void SftpChannelPrivate::handleOpenFailureInternal(const QString &reason)
+{
+ if (channelState() != SessionRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
+ }
+ emit initializationFailed(tr("Server could not start session: %1").arg(reason));
+}
+
+void SftpChannelPrivate::sendReadRequest(const SftpDownload::Ptr &job,
+ quint32 requestId)
+{
+ Q_ASSERT(job->eofId == SftpInvalidJob);
+ sendData(m_outgoingPacket.generateReadFile(job->remoteHandle, job->offset,
+ AbstractSftpPacket::MaxDataSize, requestId).rawData());
+ job->offsets[requestId] = job->offset;
+ job->offset += AbstractSftpPacket::MaxDataSize;
+ if (job->offset >= job->fileSize)
+ job->eofId = requestId;
+}
+
+void SftpChannelPrivate::reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job,
+ const QString &error)
+{
+ emit finished(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::attributesToFileInfo(const SftpFileAttributes &attributes,
+ SftpFileInfo &fileInfo) const
+{
+ if (attributes.sizePresent) {
+ fileInfo.sizeValid = true;
+ fileInfo.size = attributes.size;
+ }
+ if (attributes.permissionsPresent) {
+ if (attributes.permissions & 0x8000) // S_IFREG
+ fileInfo.type = FileTypeRegular;
+ else if (attributes.permissions & 0x4000) // S_IFDIR
+ fileInfo.type = FileTypeDirectory;
+ else
+ fileInfo.type = FileTypeOther;
+ fileInfo.permissionsValid = true;
+ fileInfo.permissions = 0;
+ if (attributes.permissions & 00001) // S_IXOTH
+ fileInfo.permissions |= QFile::ExeOther;
+ if (attributes.permissions & 00002) // S_IWOTH
+ fileInfo.permissions |= QFile::WriteOther;
+ if (attributes.permissions & 00004) // S_IROTH
+ fileInfo.permissions |= QFile::ReadOther;
+ if (attributes.permissions & 00010) // S_IXGRP
+ fileInfo.permissions |= QFile::ExeGroup;
+ if (attributes.permissions & 00020) // S_IWGRP
+ fileInfo.permissions |= QFile::WriteGroup;
+ if (attributes.permissions & 00040) // S_IRGRP
+ fileInfo.permissions |= QFile::ReadGroup;
+ if (attributes.permissions & 00100) // S_IXUSR
+ fileInfo.permissions |= QFile::ExeUser | QFile::ExeOwner;
+ if (attributes.permissions & 00200) // S_IWUSR
+ fileInfo.permissions |= QFile::WriteUser | QFile::WriteOwner;
+ if (attributes.permissions & 00400) // S_IRUSR
+ fileInfo.permissions |= QFile::ReadUser | QFile::ReadOwner;
+ }
+}
+
+void SftpChannelPrivate::removeTransferRequest(const JobMap::Iterator &it)
+{
+ --it.value().staticCast<AbstractSftpTransfer>()->inFlightCount;
+ m_jobs.erase(it);
+}
+
+void SftpChannelPrivate::sendWriteRequest(const JobMap::Iterator &it)
+{
+ SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
+ QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize);
+ if (job->localFile->error() != QFile::NoError) {
+ if (job->parentJob)
+ job->parentJob->setError();
+ reportRequestError(job, 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)
+{
+ SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>();
+ op->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
+ sendWriteRequest(it);
+ for (int i = 1; !op->hasError && i < op->inFlightCount; ++i)
+ sendWriteRequest(m_jobs.insert(++m_nextJobId, op));
+}
+
+void SftpChannelPrivate::spawnReadRequests(const SftpDownload::Ptr &job)
+{
+ job->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
+ sendReadRequest(job, job->jobId);
+ for (int i = 1; i < job->inFlightCount; ++i) {
+ const quint32 requestId = ++m_nextJobId;
+ m_jobs.insert(requestId, job);
+ sendReadRequest(job, requestId);
+ }
+}
+
+} // namespace Internal
+} // namespace QSsh
diff --git a/src/libs/ssh/sftpchannel.h b/src/libs/ssh/sftpchannel.h
new file mode 100644
index 0000000000..e1a5605023
--- /dev/null
+++ b/src/libs/ssh/sftpchannel.h
@@ -0,0 +1,113 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SFTCHANNEL_H
+#define SFTCHANNEL_H
+
+#include "sftpdefs.h"
+#include "sftpincomingpacket_p.h"
+
+#include "ssh_global.h"
+
+#include <QByteArray>
+#include <QObject>
+#include <QSharedPointer>
+#include <QString>
+
+namespace QSsh {
+
+namespace Internal {
+class SftpChannelPrivate;
+class SshChannelManager;
+class SshSendFacility;
+} // namespace Internal
+
+class QSSH_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 statFile(const QString &path);
+ 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 createLink(const QString &filePath, const QString &target);
+ SftpJobId uploadFile(const QString &localFilePath,
+ const QString &remoteFilePath, SftpOverwriteMode mode);
+ SftpJobId downloadFile(const QString &remoteFilePath,
+ const QString &localFilePath, SftpOverwriteMode mode);
+ SftpJobId uploadDir(const QString &localDirPath,
+ const QString &remoteParentDirPath);
+
+ ~SftpChannel();
+
+signals:
+ void initialized();
+ void initializationFailed(const QString &reason);
+ void closed();
+
+ // error.isEmpty <=> finished successfully
+ void finished(QSsh::SftpJobId job, const QString &error = QString());
+
+ // TODO: Also emit for each file copied by uploadDir().
+ void dataAvailable(QSsh::SftpJobId job, const QString &data);
+
+ /*
+ * This signal is emitted as a result of:
+ * - statFile() (with the list having exactly one element)
+ * - listDirectory() (potentially more than once)
+ */
+ void fileInfoAvailable(QSsh::SftpJobId job, const QList<QSsh::SftpFileInfo> &fileInfoList);
+
+private:
+ SftpChannel(quint32 channelId, Internal::SshSendFacility &sendFacility);
+
+ Internal::SftpChannelPrivate *d;
+};
+
+} // namespace QSsh
+
+#endif // SFTPCHANNEL_H
diff --git a/src/libs/ssh/sftpchannel_p.h b/src/libs/ssh/sftpchannel_p.h
new file mode 100644
index 0000000000..040a39262a
--- /dev/null
+++ b/src/libs/ssh/sftpchannel_p.h
@@ -0,0 +1,135 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SFTCHANNEL_P_H
+#define SFTCHANNEL_P_H
+
+#include "sftpdefs.h"
+#include "sftpincomingpacket_p.h"
+#include "sftpoperation_p.h"
+#include "sftpoutgoingpacket_p.h"
+#include "sshchannel_p.h"
+
+#include <QByteArray>
+#include <QMap>
+
+namespace QSsh {
+class SftpChannel;
+namespace Internal {
+
+class SftpChannelPrivate : public AbstractSshChannel
+{
+ Q_OBJECT
+ friend class QSsh::SftpChannel;
+public:
+
+ enum SftpState { Inactive, SubsystemRequested, InitSent, Initialized };
+
+ virtual void handleChannelSuccess();
+ virtual void handleChannelFailure();
+
+ virtual void closeHook();
+
+signals:
+ void initialized();
+ void initializationFailed(const QString &reason);
+ void closed();
+ void finished(QSsh::SftpJobId job, const QString &error = QString());
+ void dataAvailable(QSsh::SftpJobId job, const QString &data);
+ void fileInfoAvailable(QSsh::SftpJobId job, const QList<QSsh::SftpFileInfo> &fileInfoList);
+
+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(const QString &reason);
+ virtual void handleChannelDataInternal(const QByteArray &data);
+ virtual void handleChannelExtendedDataInternal(quint32 type,
+ const QByteArray &data);
+ virtual void handleExitStatus(const SshChannelExitStatus &exitStatus);
+ virtual void handleExitSignal(const SshChannelExitSignal &signal);
+
+ void handleCurrentPacket();
+ void handleServerVersion();
+ void handleHandle();
+ void handleStatus();
+ void handleName();
+ void handleReadData();
+ void handleAttrs();
+
+ void handleStatusGeneric(const JobMap::Iterator &it,
+ const SftpStatusResponse &response);
+ void handleMkdirStatus(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 attributesToFileInfo(const SftpFileAttributes &attributes, SftpFileInfo &fileInfo) const;
+
+ JobMap::Iterator lookupJob(SftpJobId id);
+ JobMap m_jobs;
+ SftpOutgoingPacket m_outgoingPacket;
+ SftpIncomingPacket m_incomingPacket;
+ QByteArray m_incomingData;
+ SftpJobId m_nextJobId;
+ SftpState m_sftpState;
+ SftpChannel *m_sftp;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SFTPCHANNEL_P_H
diff --git a/src/libs/ssh/sftpdefs.cpp b/src/libs/ssh/sftpdefs.cpp
new file mode 100644
index 0000000000..f8c1b957b9
--- /dev/null
+++ b/src/libs/ssh/sftpdefs.cpp
@@ -0,0 +1,35 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sftpdefs.h"
+
+namespace QSsh { const SftpJobId SftpInvalidJob = 0; }
diff --git a/src/libs/ssh/sftpdefs.h b/src/libs/ssh/sftpdefs.h
new file mode 100644
index 0000000000..c383b12472
--- /dev/null
+++ b/src/libs/ssh/sftpdefs.h
@@ -0,0 +1,69 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SFTPDEFS_H
+#define SFTPDEFS_H
+
+#include "ssh_global.h"
+
+#include <QFile>
+#include <QString>
+
+namespace QSsh {
+
+typedef quint32 SftpJobId;
+QSSH_EXPORT extern const SftpJobId SftpInvalidJob;
+
+enum SftpOverwriteMode {
+ SftpOverwriteExisting, SftpAppendToExisting, SftpSkipExisting
+};
+
+enum SftpFileType { FileTypeRegular, FileTypeDirectory, FileTypeOther, FileTypeUnknown };
+
+class QSSH_EXPORT SftpFileInfo
+{
+public:
+ SftpFileInfo() : type(FileTypeUnknown), sizeValid(false), permissionsValid(false) { }
+
+ QString name;
+ SftpFileType type;
+ quint64 size;
+ QFile::Permissions permissions;
+
+ // The RFC allows an SFTP server not to support any file attributes beyond the name.
+ bool sizeValid;
+ bool permissionsValid;
+};
+
+} // namespace QSsh
+
+#endif // SFTPDEFS_H
diff --git a/src/libs/ssh/sftpfilesystemmodel.cpp b/src/libs/ssh/sftpfilesystemmodel.cpp
new file mode 100644
index 0000000000..6d24c1b8c6
--- /dev/null
+++ b/src/libs/ssh/sftpfilesystemmodel.cpp
@@ -0,0 +1,386 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#include "sftpfilesystemmodel.h"
+
+#include "sftpchannel.h"
+#include "sshconnection.h"
+#include "sshconnectionmanager.h"
+
+#include <QFileInfo>
+#include <QHash>
+#include <QIcon>
+#include <QList>
+#include <QString>
+
+namespace QSsh {
+namespace Internal {
+namespace {
+
+class SftpDirNode;
+class SftpFileNode
+{
+public:
+ SftpFileNode() : parent(0) { }
+ virtual ~SftpFileNode() { }
+
+ QString path;
+ SftpFileInfo fileInfo;
+ SftpDirNode *parent;
+};
+
+class SftpDirNode : public SftpFileNode
+{
+public:
+ SftpDirNode() : lsState(LsNotYetCalled) { }
+ ~SftpDirNode() { qDeleteAll(children); }
+
+ enum { LsNotYetCalled, LsRunning, LsFinished } lsState;
+ QList<SftpFileNode *> children;
+};
+
+typedef QHash<SftpJobId, SftpDirNode *> DirNodeHash;
+
+SftpFileNode *indexToFileNode(const QModelIndex &index)
+{
+ return static_cast<SftpFileNode *>(index.internalPointer());
+}
+
+SftpDirNode *indexToDirNode(const QModelIndex &index)
+{
+ SftpFileNode * const fileNode = indexToFileNode(index);
+ QSSH_ASSERT(fileNode);
+ return dynamic_cast<SftpDirNode *>(fileNode);
+}
+
+} // anonymous namespace
+
+class SftpFileSystemModelPrivate
+{
+public:
+ SshConnection::Ptr sshConnection;
+ SftpChannel::Ptr sftpChannel;
+ QString rootDirectory;
+ SftpFileNode *rootNode;
+ SftpJobId statJobId;
+ DirNodeHash lsOps;
+ QList<SftpJobId> externalJobs;
+};
+} // namespace Internal
+
+using namespace Internal;
+
+SftpFileSystemModel::SftpFileSystemModel(QObject *parent)
+ : QAbstractItemModel(parent), d(new SftpFileSystemModelPrivate)
+{
+ d->rootDirectory = QLatin1String("/");
+ d->rootNode = 0;
+ d->statJobId = SftpInvalidJob;
+}
+
+SftpFileSystemModel::~SftpFileSystemModel()
+{
+ shutDown();
+ delete d;
+}
+
+void SftpFileSystemModel::setSshConnection(const SshConnectionParameters &sshParams)
+{
+ QSSH_ASSERT_AND_RETURN(!d->sshConnection);
+ d->sshConnection = SshConnectionManager::instance().acquireConnection(sshParams);
+ connect(d->sshConnection.data(), SIGNAL(error(QSsh::SshError)),
+ SLOT(handleSshConnectionFailure()));
+ if (d->sshConnection->state() == SshConnection::Connected) {
+ handleSshConnectionEstablished();
+ return;
+ }
+ connect(d->sshConnection.data(), SIGNAL(connected()), SLOT(handleSshConnectionEstablished()));
+ if (d->sshConnection->state() == SshConnection::Unconnected)
+ d->sshConnection->connectToHost();
+}
+
+void SftpFileSystemModel::setRootDirectory(const QString &path)
+{
+ beginResetModel();
+ d->rootDirectory = path;
+ delete d->rootNode;
+ d->rootNode = 0;
+ d->lsOps.clear();
+ d->statJobId = SftpInvalidJob;
+ endResetModel();
+ statRootDirectory();
+}
+
+QString SftpFileSystemModel::rootDirectory() const
+{
+ return d->rootDirectory;
+}
+
+SftpJobId SftpFileSystemModel::downloadFile(const QModelIndex &index, const QString &targetFilePath)
+{
+ QSSH_ASSERT_AND_RETURN_VALUE(d->rootNode, SftpInvalidJob);
+ const SftpFileNode * const fileNode = indexToFileNode(index);
+ QSSH_ASSERT_AND_RETURN_VALUE(fileNode, SftpInvalidJob);
+ QSSH_ASSERT_AND_RETURN_VALUE(fileNode->fileInfo.type == FileTypeRegular, SftpInvalidJob);
+ const SftpJobId jobId = d->sftpChannel->downloadFile(fileNode->path, targetFilePath,
+ SftpOverwriteExisting);
+ if (jobId != SftpInvalidJob)
+ d->externalJobs << jobId;
+ return jobId;
+}
+
+int SftpFileSystemModel::columnCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent);
+ return 2; // type + name
+}
+
+QVariant SftpFileSystemModel::data(const QModelIndex &index, int role) const
+{
+ const SftpFileNode * const node = indexToFileNode(index);
+ if (index.column() == 0 && role == Qt::DecorationRole) {
+ switch (node->fileInfo.type) {
+ case FileTypeRegular:
+ case FileTypeOther:
+ return QIcon(QLatin1String(":/core/images/unknownfile.png"));
+ case FileTypeDirectory:
+ return QIcon(QLatin1String(":/core/images/dir.png"));
+ case FileTypeUnknown:
+ return QIcon(QLatin1String(":/core/images/help.png")); // Shows a question mark.
+ }
+ }
+ if (index.column() == 1) {
+ if (role == Qt::DisplayRole)
+ return node->fileInfo.name;
+ if (role == PathRole)
+ return node->path;
+ }
+ return QVariant();
+}
+
+Qt::ItemFlags SftpFileSystemModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::NoItemFlags;
+ return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+}
+
+QVariant SftpFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation != Qt::Horizontal)
+ return QVariant();
+ if (role != Qt::DisplayRole)
+ return QVariant();
+ if (section == 0)
+ return tr("File Type");
+ if (section == 1)
+ return tr("File Name");
+ return QVariant();
+}
+
+QModelIndex SftpFileSystemModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent))
+ return QModelIndex();
+ if (!d->rootNode)
+ return QModelIndex();
+ if (!parent.isValid())
+ return createIndex(row, column, d->rootNode);
+ const SftpDirNode * const parentNode = indexToDirNode(parent);
+ QSSH_ASSERT_AND_RETURN_VALUE(parentNode, QModelIndex());
+ QSSH_ASSERT_AND_RETURN_VALUE(row < parentNode->children.count(), QModelIndex());
+ SftpFileNode * const childNode = parentNode->children.at(row);
+ return createIndex(row, column, childNode);
+}
+
+QModelIndex SftpFileSystemModel::parent(const QModelIndex &child) const
+{
+ if (!child.isValid()) // Don't assert on this, since the model tester tries it.
+ return QModelIndex();
+
+ const SftpFileNode * const childNode = indexToFileNode(child);
+ QSSH_ASSERT_AND_RETURN_VALUE(childNode, QModelIndex());
+ if (childNode == d->rootNode)
+ return QModelIndex();
+ SftpDirNode * const parentNode = childNode->parent;
+ if (parentNode == d->rootNode)
+ return createIndex(0, 0, d->rootNode);
+ const SftpDirNode * const grandParentNode = parentNode->parent;
+ QSSH_ASSERT_AND_RETURN_VALUE(grandParentNode, QModelIndex());
+ return createIndex(grandParentNode->children.indexOf(parentNode), 0, parentNode);
+}
+
+int SftpFileSystemModel::rowCount(const QModelIndex &parent) const
+{
+ if (!d->rootNode)
+ return 0;
+ if (!parent.isValid())
+ return 1;
+ if (parent.column() != 0)
+ return 0;
+ SftpDirNode * const dirNode = indexToDirNode(parent);
+ if (!dirNode)
+ return 0;
+ if (dirNode->lsState != SftpDirNode::LsNotYetCalled)
+ return dirNode->children.count();
+ d->lsOps.insert(d->sftpChannel->listDirectory(dirNode->path), dirNode);
+ dirNode->lsState = SftpDirNode::LsRunning;
+ return 0;
+}
+
+void SftpFileSystemModel::statRootDirectory()
+{
+ d->statJobId = d->sftpChannel->statFile(d->rootDirectory);
+}
+
+void SftpFileSystemModel::shutDown()
+{
+ if (d->sftpChannel) {
+ disconnect(d->sftpChannel.data(), 0, this, 0);
+ d->sftpChannel->closeChannel();
+ d->sftpChannel.clear();
+ }
+ if (d->sshConnection) {
+ disconnect(d->sshConnection.data(), 0, this, 0);
+ SshConnectionManager::instance().releaseConnection(d->sshConnection);
+ d->sshConnection.clear();
+ }
+ delete d->rootNode;
+ d->rootNode = 0;
+}
+
+void SftpFileSystemModel::handleSshConnectionFailure()
+{
+ emit connectionError(d->sshConnection->errorString());
+ beginResetModel();
+ shutDown();
+ endResetModel();
+}
+
+void SftpFileSystemModel::handleSftpChannelInitialized()
+{
+ connect(d->sftpChannel.data(),
+ SIGNAL(fileInfoAvailable(QSsh::SftpJobId,QList<QSsh::SftpFileInfo>)),
+ SLOT(handleFileInfo(QSsh::SftpJobId,QList<QSsh::SftpFileInfo>)));
+ connect(d->sftpChannel.data(), SIGNAL(finished(QSsh::SftpJobId,QString)),
+ SLOT(handleSftpJobFinished(QSsh::SftpJobId,QString)));
+ statRootDirectory();
+}
+
+void SftpFileSystemModel::handleSshConnectionEstablished()
+{
+ d->sftpChannel = d->sshConnection->createSftpChannel();
+ connect(d->sftpChannel.data(), SIGNAL(initialized()), SLOT(handleSftpChannelInitialized()));
+ connect(d->sftpChannel.data(), SIGNAL(initializationFailed(QString)),
+ SLOT(handleSftpChannelInitializationFailed(QString)));
+ d->sftpChannel->initialize();
+}
+
+void SftpFileSystemModel::handleSftpChannelInitializationFailed(const QString &reason)
+{
+ emit connectionError(reason);
+ beginResetModel();
+ shutDown();
+ endResetModel();
+}
+
+void SftpFileSystemModel::handleFileInfo(SftpJobId jobId, const QList<SftpFileInfo> &fileInfoList)
+{
+ if (jobId == d->statJobId) {
+ QSSH_ASSERT_AND_RETURN(!d->rootNode);
+ beginInsertRows(QModelIndex(), 0, 0);
+ d->rootNode = new SftpDirNode;
+ d->rootNode->path = d->rootDirectory;
+ d->rootNode->fileInfo = fileInfoList.first();
+ d->rootNode->fileInfo.name = d->rootDirectory == QLatin1String("/")
+ ? d->rootDirectory : QFileInfo(d->rootDirectory).fileName();
+ endInsertRows();
+ return;
+ }
+ SftpDirNode * const parentNode = d->lsOps.value(jobId);
+ QSSH_ASSERT_AND_RETURN(parentNode);
+ QList<SftpFileInfo> filteredList;
+ foreach (const SftpFileInfo &fi, fileInfoList) {
+ if (fi.name != QLatin1String(".") && fi.name != QLatin1String(".."))
+ filteredList << fi;
+ }
+ if (filteredList.isEmpty())
+ return;
+
+ // In theory beginInsertRows() should suffice, but that fails to have an effect
+ // if rowCount() returned 0 earlier.
+ emit layoutAboutToBeChanged();
+
+ foreach (const SftpFileInfo &fileInfo, filteredList) {
+ SftpFileNode *childNode;
+ if (fileInfo.type == FileTypeDirectory)
+ childNode = new SftpDirNode;
+ else
+ childNode = new SftpFileNode;
+ childNode->path = parentNode->path;
+ if (!childNode->path.endsWith(QLatin1Char('/')))
+ childNode->path += QLatin1Char('/');
+ childNode->path += fileInfo.name;
+ childNode->fileInfo = fileInfo;
+ childNode->parent = parentNode;
+ parentNode->children << childNode;
+ }
+ emit layoutChanged(); // Should be endInsertRows(), see above.
+}
+
+void SftpFileSystemModel::handleSftpJobFinished(SftpJobId jobId, const QString &errorMessage)
+{
+ if (jobId == d->statJobId) {
+ d->statJobId = SftpInvalidJob;
+ if (!errorMessage.isEmpty())
+ emit sftpOperationFailed(tr("Error getting 'stat' info about '%1': %2")
+ .arg(rootDirectory(), errorMessage));
+ return;
+ }
+
+ DirNodeHash::Iterator it = d->lsOps.find(jobId);
+ if (it != d->lsOps.end()) {
+ QSSH_ASSERT(it.value()->lsState == SftpDirNode::LsRunning);
+ it.value()->lsState = SftpDirNode::LsFinished;
+ if (!errorMessage.isEmpty())
+ emit sftpOperationFailed(tr("Error listing contents of directory '%1': %2")
+ .arg(it.value()->path, errorMessage));
+ d->lsOps.erase(it);
+ return;
+ }
+
+ const int jobIndex = d->externalJobs.indexOf(jobId);
+ QSSH_ASSERT_AND_RETURN(jobIndex != -1);
+ d->externalJobs.removeAt(jobIndex);
+ emit sftpOperationFinished(jobId, errorMessage);
+}
+
+} // namespace QSsh
diff --git a/src/libs/ssh/sftpfilesystemmodel.h b/src/libs/ssh/sftpfilesystemmodel.h
new file mode 100644
index 0000000000..43690cff3f
--- /dev/null
+++ b/src/libs/ssh/sftpfilesystemmodel.h
@@ -0,0 +1,110 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#ifndef SFTPFILESYSTEMMODEL_H
+#define SFTPFILESYSTEMMODEL_H
+
+#include "sftpdefs.h"
+
+#include "ssh_global.h"
+
+#include <QAbstractItemModel>
+
+namespace QSsh {
+class SshConnectionParameters;
+
+namespace Internal { class SftpFileSystemModelPrivate; }
+
+// Very simple read-only model. Symbolic links are not followed.
+class QSSH_EXPORT SftpFileSystemModel : public QAbstractItemModel
+{
+ Q_OBJECT
+public:
+ // Use this to get the full path of a file or directory via data().
+ static const int PathRole = Qt::UserRole;
+
+ explicit SftpFileSystemModel(QObject *parent = 0);
+ ~SftpFileSystemModel();
+
+ /*
+ * Once this is called, an SFTP connection is established and the model is populated.
+ * The effect of additional calls is undefined.
+ */
+ void setSshConnection(const SshConnectionParameters &sshParams);
+
+ void setRootDirectory(const QString &path); // Default is "/".
+ QString rootDirectory() const;
+
+ SftpJobId downloadFile(const QModelIndex &index, const QString &targetFilePath);
+
+signals:
+ /*
+ * E.g. "Permission denied". Note that this can happen without direct user intervention,
+ * due to e.g. the view calling rowCount() on a non-readable directory. This signal should
+ * therefore not result in a message box or similar, since it might occur very often.
+ */
+ void sftpOperationFailed(const QString &errorMessage);
+
+ /*
+ * This error is not recoverable. The model will not have any content after
+ * the signal has been emitted.
+ */
+ void connectionError(const QString &errorMessage);
+
+ // Success <=> error.isEmpty().
+ void sftpOperationFinished(QSsh::SftpJobId, const QString &error);
+
+private slots:
+ void handleSshConnectionEstablished();
+ void handleSshConnectionFailure();
+ void handleSftpChannelInitialized();
+ void handleSftpChannelInitializationFailed(const QString &reason);
+ void handleFileInfo(QSsh::SftpJobId jobId, const QList<QSsh::SftpFileInfo> &fileInfoList);
+ void handleSftpJobFinished(QSsh::SftpJobId jobId, const QString &errorMessage);
+
+private:
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &child) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+
+ void statRootDirectory();
+ void shutDown();
+
+ Internal::SftpFileSystemModelPrivate * const d;
+};
+
+} // namespace QSsh;
+
+#endif // SFTPFILESYSTEMMODEL_H
diff --git a/src/libs/ssh/sftpincomingpacket.cpp b/src/libs/ssh/sftpincomingpacket.cpp
new file mode 100644
index 0000000000..8b878bb699
--- /dev/null
+++ b/src/libs/ssh/sftpincomingpacket.cpp
@@ -0,0 +1,225 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sftpincomingpacket_p.h"
+
+#include "sshexception_p.h"
+#include "sshpacketparser_p.h"
+
+namespace QSsh {
+namespace Internal {
+
+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 QSsh
diff --git a/src/libs/ssh/sftpincomingpacket_p.h b/src/libs/ssh/sftpincomingpacket_p.h
new file mode 100644
index 0000000000..7f62ffcdce
--- /dev/null
+++ b/src/libs/ssh/sftpincomingpacket_p.h
@@ -0,0 +1,114 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SFTPINCOMINGPACKET_P_H
+#define SFTPINCOMINGPACKET_P_H
+
+#include "sftppacket_p.h"
+
+namespace QSsh {
+namespace Internal {
+
+struct SftpHandleResponse {
+ quint32 requestId;
+ QByteArray handle;
+};
+
+struct SftpStatusResponse {
+ quint32 requestId;
+ SftpStatusCode status;
+ QString errorString;
+ QByteArray language;
+};
+
+struct SftpFileAttributes {
+ bool sizePresent;
+ bool timesPresent;
+ bool uidAndGidPresent;
+ bool permissionsPresent;
+ quint64 size;
+ quint32 uid;
+ quint32 gid;
+ quint32 permissions;
+ quint32 atime;
+ quint32 mtime;
+};
+
+struct SftpFile {
+ QString fileName;
+ QString longName; // Not present in later RFCs, so we don't expose this to the user.
+ SftpFileAttributes attributes;
+};
+
+struct SftpNameResponse {
+ quint32 requestId;
+ QList<SftpFile> files;
+};
+
+struct SftpDataResponse {
+ quint32 requestId;
+ QByteArray data;
+};
+
+struct SftpAttrsResponse {
+ quint32 requestId;
+ SftpFileAttributes attrs;
+};
+
+class SftpIncomingPacket : public AbstractSftpPacket
+{
+public:
+ SftpIncomingPacket();
+
+ void consumeData(QByteArray &data);
+ void clear();
+ bool isComplete() const;
+ quint32 extractServerVersion() const;
+ SftpHandleResponse asHandleResponse() const;
+ SftpStatusResponse asStatusResponse() const;
+ SftpNameResponse asNameResponse() const;
+ SftpDataResponse asDataResponse() const;
+ SftpAttrsResponse asAttrsResponse() const;
+
+private:
+ void moveFirstBytes(QByteArray &target, QByteArray &source, int n);
+
+ SftpFileAttributes asFileAttributes(quint32 &offset) const;
+ SftpFile asFile(quint32 &offset) const;
+
+ quint32 m_length;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SFTPINCOMINGPACKET_P_H
diff --git a/src/libs/ssh/sftpoperation.cpp b/src/libs/ssh/sftpoperation.cpp
new file mode 100644
index 0000000000..1ad62e0dfa
--- /dev/null
+++ b/src/libs/ssh/sftpoperation.cpp
@@ -0,0 +1,227 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sftpoperation_p.h"
+
+#include "sftpoutgoingpacket_p.h"
+
+#include <QFile>
+
+namespace QSsh {
+namespace Internal {
+
+AbstractSftpOperation::AbstractSftpOperation(SftpJobId jobId) : jobId(jobId)
+{
+}
+
+AbstractSftpOperation::~AbstractSftpOperation() { }
+
+
+SftpStatFile::SftpStatFile(SftpJobId jobId, const QString &path)
+ : AbstractSftpOperation(jobId), path(path)
+{
+}
+
+SftpOutgoingPacket &SftpStatFile::initialPacket(SftpOutgoingPacket &packet)
+{
+ return packet.generateStat(path, jobId);
+}
+
+SftpMakeDir::SftpMakeDir(SftpJobId jobId, const QString &path,
+ const SftpUploadDir::Ptr &parentJob)
+ : AbstractSftpOperation(jobId), parentJob(parentJob), remoteDir(path)
+{
+}
+
+SftpOutgoingPacket &SftpMakeDir::initialPacket(SftpOutgoingPacket &packet)
+{
+ return packet.generateMkDir(remoteDir, jobId);
+}
+
+
+SftpRmDir::SftpRmDir(SftpJobId, 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);
+}
+
+
+SftpCreateLink::SftpCreateLink(SftpJobId jobId, const QString &filePath, const QString &target)
+ : AbstractSftpOperation(jobId), filePath(filePath), target(target)
+{
+}
+
+SftpOutgoingPacket &SftpCreateLink::initialPacket(SftpOutgoingPacket &packet)
+{
+ return packet.generateCreateLink(filePath, target, jobId);
+}
+
+
+AbstractSftpOperationWithHandle::AbstractSftpOperationWithHandle(SftpJobId jobId,
+ const QString &remotePath)
+ : AbstractSftpOperation(jobId),
+ remotePath(remotePath), state(Inactive), hasError(false)
+{
+}
+
+AbstractSftpOperationWithHandle::~AbstractSftpOperationWithHandle() { }
+
+
+SftpListDir::SftpListDir(SftpJobId jobId, const QString &path)
+ : 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,
+ SftpOutgoingPacket::DefaultPermissions, 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)
+{
+}
+
+AbstractSftpTransfer::~AbstractSftpTransfer() {}
+
+void AbstractSftpTransfer::calculateInFlightCount(quint32 chunkSize)
+{
+ if (fileSize == 0) {
+ inFlightCount = 1;
+ } else {
+ inFlightCount = fileSize / chunkSize;
+ if (fileSize % chunkSize)
+ ++inFlightCount;
+ if (inFlightCount > MaxInFlightCount)
+ inFlightCount = MaxInFlightCount;
+ }
+}
+
+
+SftpDownload::SftpDownload(SftpJobId jobId, const QString &remotePath,
+ const QSharedPointer<QFile> &localFile)
+ : AbstractSftpTransfer(jobId, remotePath, localFile), eofId(SftpInvalidJob)
+{
+}
+
+SftpOutgoingPacket &SftpDownload::initialPacket(SftpOutgoingPacket &packet)
+{
+ state = OpenRequested;
+ return packet.generateOpenFileForReading(remotePath, jobId);
+}
+
+
+SftpUploadFile::SftpUploadFile(SftpJobId jobId, const QString &remotePath,
+ const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode,
+ const SftpUploadDir::Ptr &parentJob)
+ : AbstractSftpTransfer(jobId, remotePath, localFile),
+ parentJob(parentJob), mode(mode)
+{
+ fileSize = localFile->size();
+}
+
+SftpOutgoingPacket &SftpUploadFile::initialPacket(SftpOutgoingPacket &packet)
+{
+ state = OpenRequested;
+ quint32 permissions = 0;
+ const QFile::Permissions &qtPermissions = localFile->permissions();
+ if (qtPermissions & QFile::ExeOther)
+ permissions |= 1 << 0;
+ if (qtPermissions & QFile::WriteOther)
+ permissions |= 1 << 1;
+ if (qtPermissions & QFile::ReadOther)
+ permissions |= 1 << 2;
+ if (qtPermissions & QFile::ExeGroup)
+ permissions |= 1<< 3;
+ if (qtPermissions & QFile::WriteGroup)
+ permissions |= 1<< 4;
+ if (qtPermissions & QFile::ReadGroup)
+ permissions |= 1<< 5;
+ if (qtPermissions & QFile::ExeOwner)
+ permissions |= 1<< 6;
+ if (qtPermissions & QFile::WriteOwner)
+ permissions |= 1<< 7;
+ if (qtPermissions & QFile::ReadOwner)
+ permissions |= 1<< 8;
+ return packet.generateOpenFileForWriting(remotePath, mode, permissions, jobId);
+}
+
+SftpUploadDir::~SftpUploadDir() {}
+
+} // namespace Internal
+} // namespace QSsh
diff --git a/src/libs/ssh/sftpoperation_p.h b/src/libs/ssh/sftpoperation_p.h
new file mode 100644
index 0000000000..43baa4fb6f
--- /dev/null
+++ b/src/libs/ssh/sftpoperation_p.h
@@ -0,0 +1,254 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SFTPOPERATION_P_H
+#define SFTPOPERATION_P_H
+
+#include "sftpdefs.h"
+
+#include <QByteArray>
+#include <QList>
+#include <QMap>
+#include <QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QFile;
+QT_END_NAMESPACE
+
+namespace QSsh {
+namespace Internal {
+
+class SftpOutgoingPacket;
+
+struct AbstractSftpOperation
+{
+ typedef QSharedPointer<AbstractSftpOperation> Ptr;
+ enum Type {
+ StatFile, ListDir, MakeDir, RmDir, Rm, Rename, CreateLink, CreateFile, Download, UploadFile
+ };
+
+ AbstractSftpOperation(SftpJobId jobId);
+ virtual ~AbstractSftpOperation();
+ virtual Type type() const = 0;
+ virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet) = 0;
+
+ const SftpJobId jobId;
+
+private:
+ AbstractSftpOperation(const AbstractSftpOperation &);
+ AbstractSftpOperation &operator=(const AbstractSftpOperation &);
+};
+
+struct SftpUploadDir;
+
+struct SftpStatFile : public AbstractSftpOperation
+{
+ typedef QSharedPointer<SftpStatFile> Ptr;
+
+ SftpStatFile(SftpJobId jobId, const QString &path);
+ virtual Type type() const { return StatFile; }
+ virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+ const QString path;
+};
+
+struct SftpMakeDir : public AbstractSftpOperation
+{
+ typedef QSharedPointer<SftpMakeDir> Ptr;
+
+ SftpMakeDir(SftpJobId jobId, const QString &path,
+ const QSharedPointer<SftpUploadDir> &parentJob = QSharedPointer<SftpUploadDir>());
+ virtual Type type() const { return MakeDir; }
+ virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+ const QSharedPointer<SftpUploadDir> parentJob;
+ const QString remoteDir;
+};
+
+struct SftpRmDir : public AbstractSftpOperation
+{
+ typedef QSharedPointer<SftpRmDir> Ptr;
+
+ SftpRmDir(SftpJobId 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 SftpCreateLink : public AbstractSftpOperation
+{
+ typedef QSharedPointer<SftpCreateLink> Ptr;
+
+ SftpCreateLink(SftpJobId jobId, const QString &filePath, const QString &target);
+ virtual Type type() const { return CreateLink; }
+ virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+ const QString filePath;
+ const QString target;
+};
+
+
+struct AbstractSftpOperationWithHandle : public AbstractSftpOperation
+{
+ typedef QSharedPointer<AbstractSftpOperationWithHandle> Ptr;
+ enum State { Inactive, OpenRequested, Open, CloseRequested };
+
+ AbstractSftpOperationWithHandle(SftpJobId jobId, const QString &remotePath);
+ ~AbstractSftpOperationWithHandle();
+
+ const QString remotePath;
+ QByteArray remoteHandle;
+ State state;
+ bool hasError;
+};
+
+
+struct SftpListDir : public AbstractSftpOperationWithHandle
+{
+ typedef QSharedPointer<SftpListDir> Ptr;
+
+ SftpListDir(SftpJobId jobId, const QString &path);
+ 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);
+ ~AbstractSftpTransfer();
+ 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, quint64> offsets;
+ SftpJobId eofId;
+};
+
+struct SftpUploadFile : public AbstractSftpTransfer
+{
+ typedef QSharedPointer<SftpUploadFile> Ptr;
+
+ SftpUploadFile(SftpJobId jobId, const QString &remotePath,
+ const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode,
+ const QSharedPointer<SftpUploadDir> &parentJob = QSharedPointer<SftpUploadDir>());
+ virtual Type type() const { return UploadFile; }
+ virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
+
+ const QSharedPointer<SftpUploadDir> parentJob;
+ SftpOverwriteMode mode;
+};
+
+// Composite operation.
+struct SftpUploadDir
+{
+ typedef QSharedPointer<SftpUploadDir> Ptr;
+
+ struct Dir {
+ Dir(const QString &l, const QString &r) : localDir(l), remoteDir(r) {}
+ QString localDir;
+ QString remoteDir;
+ };
+
+ SftpUploadDir(SftpJobId jobId) : jobId(jobId), hasError(false) {}
+ ~SftpUploadDir();
+
+ void setError()
+ {
+ hasError = true;
+ uploadsInProgress.clear();
+ mkdirsInProgress.clear();
+ }
+
+ const SftpJobId jobId;
+ bool hasError;
+ QList<SftpUploadFile::Ptr> uploadsInProgress;
+ QMap<SftpMakeDir::Ptr, Dir> mkdirsInProgress;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SFTPOPERATION_P_H
diff --git a/src/libs/ssh/sftpoutgoingpacket.cpp b/src/libs/ssh/sftpoutgoingpacket.cpp
new file mode 100644
index 0000000000..722cc98006
--- /dev/null
+++ b/src/libs/ssh/sftpoutgoingpacket.cpp
@@ -0,0 +1,229 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sftpoutgoingpacket_p.h"
+
+#include "sshpacket_p.h"
+
+#include <QtEndian>
+
+#include <limits>
+
+namespace QSsh {
+namespace Internal {
+
+namespace {
+ const quint32 DefaultAttributes = 0;
+ const quint32 SSH_FXF_READ = 0x00000001;
+ const quint32 SSH_FXF_WRITE = 0x00000002;
+ const quint32 SSH_FXF_APPEND = 0x00000004;
+ const quint32 SSH_FXF_CREAT = 0x00000008;
+ const quint32 SSH_FXF_TRUNC = 0x00000010;
+ const quint32 SSH_FXF_EXCL = 0x00000020;
+}
+
+SftpOutgoingPacket::SftpOutgoingPacket()
+{
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateInit(quint32 version)
+{
+ return init(SSH_FXP_INIT, 0).appendInt(version).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateStat(const QString &path, quint32 requestId)
+{
+ return init(SSH_FXP_LSTAT, requestId).appendString(path).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateOpenDir(const QString &path,
+ quint32 requestId)
+{
+ return init(SSH_FXP_OPENDIR, requestId).appendString(path).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateReadDir(const QByteArray &handle,
+ quint32 requestId)
+{
+ return init(SSH_FXP_READDIR, requestId).appendString(handle).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateCloseHandle(const QByteArray &handle,
+ quint32 requestId)
+{
+ return init(SSH_FXP_CLOSE, requestId).appendString(handle).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateMkDir(const QString &path,
+ quint32 requestId)
+{
+ return init(SSH_FXP_MKDIR, requestId).appendString(path)
+ .appendInt(DefaultAttributes).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateRmDir(const QString &path,
+ quint32 requestId)
+{
+ return init(SSH_FXP_RMDIR, requestId).appendString(path).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateRm(const QString &path,
+ quint32 requestId)
+{
+ return init(SSH_FXP_REMOVE, requestId).appendString(path).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateRename(const QString &oldPath,
+ const QString &newPath, quint32 requestId)
+{
+ return init(SSH_FXP_RENAME, requestId).appendString(oldPath)
+ .appendString(newPath).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFileForWriting(const QString &path,
+ SftpOverwriteMode mode, quint32 permissions, quint32 requestId)
+{
+ QList<quint32> attributes;
+ if (permissions != DefaultPermissions)
+ attributes << SSH_FILEXFER_ATTR_PERMISSIONS << permissions;
+ else
+ attributes << DefaultAttributes;
+ return generateOpenFile(path, Write, mode, attributes, requestId);
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFileForReading(const QString &path,
+ quint32 requestId)
+{
+ // Note: Overwrite mode is irrelevant and will be ignored.
+ return generateOpenFile(path, Read, SftpSkipExisting, QList<quint32>() << DefaultAttributes,
+ requestId);
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateReadFile(const QByteArray &handle,
+ quint64 offset, quint32 length, quint32 requestId)
+{
+ return init(SSH_FXP_READ, requestId).appendString(handle).appendInt64(offset)
+ .appendInt(length).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateFstat(const QByteArray &handle,
+ quint32 requestId)
+{
+ return init(SSH_FXP_FSTAT, requestId).appendString(handle).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateWriteFile(const QByteArray &handle,
+ quint64 offset, const QByteArray &data, quint32 requestId)
+{
+ return init(SSH_FXP_WRITE, requestId).appendString(handle)
+ .appendInt64(offset).appendString(data).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateCreateLink(const QString &filePath,
+ const QString &target, quint32 requestId)
+{
+ return init(SSH_FXP_SYMLINK, requestId).appendString(filePath).appendString(target).finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFile(const QString &path,
+ OpenType openType, SftpOverwriteMode mode, const QList<quint32> &attributes, quint32 requestId)
+{
+ quint32 pFlags;
+ switch (openType) {
+ case Read:
+ pFlags = SSH_FXF_READ;
+ break;
+ case Write:
+ pFlags = SSH_FXF_WRITE | SSH_FXF_CREAT;
+ switch (mode) {
+ case SftpOverwriteExisting: pFlags |= SSH_FXF_TRUNC; break;
+ case SftpAppendToExisting: pFlags |= SSH_FXF_APPEND; break;
+ case SftpSkipExisting: pFlags |= SSH_FXF_EXCL; break;
+ }
+ break;
+ }
+
+ init(SSH_FXP_OPEN, requestId).appendString(path).appendInt(pFlags);
+ foreach (const quint32 attribute, attributes)
+ appendInt(attribute);
+ return finalize();
+}
+
+SftpOutgoingPacket &SftpOutgoingPacket::init(SftpPacketType type,
+ quint32 requestId)
+{
+ m_data.resize(TypeOffset + 1);
+ m_data[TypeOffset] = type;
+ if (type != SSH_FXP_INIT) {
+ appendInt(requestId);
+#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;
+}
+
+const quint32 SftpOutgoingPacket::DefaultPermissions = std::numeric_limits<quint32>::max();
+
+} // namespace Internal
+} // namespace QSsh
diff --git a/src/libs/ssh/sftpoutgoingpacket_p.h b/src/libs/ssh/sftpoutgoingpacket_p.h
new file mode 100644
index 0000000000..d53f2dcc0e
--- /dev/null
+++ b/src/libs/ssh/sftpoutgoingpacket_p.h
@@ -0,0 +1,94 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SFTPOUTGOINGPACKET_P_H
+#define SFTPOUTGOINGPACKET_P_H
+
+#include "sftppacket_p.h"
+#include "sftpdefs.h"
+
+namespace QSsh {
+namespace Internal {
+
+class SftpOutgoingPacket : public AbstractSftpPacket
+{
+public:
+ SftpOutgoingPacket();
+ SftpOutgoingPacket &generateInit(quint32 version);
+ SftpOutgoingPacket &generateStat(const QString &path, quint32 requestId);
+ SftpOutgoingPacket &generateOpenDir(const QString &path, quint32 requestId);
+ SftpOutgoingPacket &generateReadDir(const QByteArray &handle,
+ quint32 requestId);
+ SftpOutgoingPacket &generateCloseHandle(const QByteArray &handle,
+ quint32 requestId);
+ SftpOutgoingPacket &generateMkDir(const QString &path, quint32 requestId);
+ SftpOutgoingPacket &generateRmDir(const QString &path, quint32 requestId);
+ SftpOutgoingPacket &generateRm(const QString &path, quint32 requestId);
+ SftpOutgoingPacket &generateRename(const QString &oldPath,
+ const QString &newPath, quint32 requestId);
+ SftpOutgoingPacket &generateOpenFileForWriting(const QString &path,
+ SftpOverwriteMode mode, quint32 permissions, quint32 requestId);
+ SftpOutgoingPacket &generateOpenFileForReading(const QString &path,
+ quint32 requestId);
+ SftpOutgoingPacket &generateReadFile(const QByteArray &handle,
+ quint64 offset, quint32 length, quint32 requestId);
+ SftpOutgoingPacket &generateFstat(const QByteArray &handle,
+ quint32 requestId);
+ SftpOutgoingPacket &generateWriteFile(const QByteArray &handle,
+ quint64 offset, const QByteArray &data, quint32 requestId);
+
+ // Note: OpenSSH's SFTP server has a bug that reverses the filePath and target
+ // arguments, so this operation is not portable.
+ SftpOutgoingPacket &generateCreateLink(const QString &filePath, const QString &target,
+ quint32 requestId);
+
+ static const quint32 DefaultPermissions;
+
+private:
+ static QByteArray encodeString(const QString &string);
+
+ enum OpenType { Read, Write };
+ SftpOutgoingPacket &generateOpenFile(const QString &path, OpenType openType,
+ SftpOverwriteMode mode, const QList<quint32> &attributes, quint32 requestId);
+
+ SftpOutgoingPacket &init(SftpPacketType type, quint32 requestId);
+ SftpOutgoingPacket &appendInt(quint32 value);
+ SftpOutgoingPacket &appendInt64(quint64 value);
+ SftpOutgoingPacket &appendString(const QString &string);
+ SftpOutgoingPacket &appendString(const QByteArray &string);
+ SftpOutgoingPacket &finalize();
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SFTPOUTGOINGPACKET_P_H
diff --git a/src/libs/ssh/sftppacket.cpp b/src/libs/ssh/sftppacket.cpp
new file mode 100644
index 0000000000..205f7ae0ca
--- /dev/null
+++ b/src/libs/ssh/sftppacket.cpp
@@ -0,0 +1,56 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sftppacket_p.h"
+
+#include "sshpacketparser_p.h"
+
+namespace QSsh {
+namespace Internal {
+
+const quint32 AbstractSftpPacket::MaxDataSize = 32000;
+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 QSsh
diff --git a/src/libs/ssh/sftppacket_p.h b/src/libs/ssh/sftppacket_p.h
new file mode 100644
index 0000000000..2a8345f7b5
--- /dev/null
+++ b/src/libs/ssh/sftppacket_p.h
@@ -0,0 +1,119 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SFTPPACKET_P_H
+#define SFTPPACKET_P_H
+
+#include <QByteArray>
+#include <QList>
+#include <QString>
+
+namespace QSsh {
+namespace Internal {
+
+enum SftpPacketType {
+ SSH_FXP_INIT = 1,
+ SSH_FXP_VERSION = 2,
+ SSH_FXP_OPEN = 3,
+ SSH_FXP_CLOSE = 4,
+ SSH_FXP_READ = 5,
+ SSH_FXP_WRITE = 6,
+ SSH_FXP_LSTAT = 7,
+ SSH_FXP_FSTAT = 8,
+ SSH_FXP_SETSTAT = 9,
+ SSH_FXP_FSETSTAT = 10,
+ SSH_FXP_OPENDIR = 11,
+ SSH_FXP_READDIR = 12,
+ SSH_FXP_REMOVE = 13,
+ SSH_FXP_MKDIR = 14,
+ SSH_FXP_RMDIR = 15,
+ SSH_FXP_REALPATH = 16,
+ SSH_FXP_STAT = 17,
+ SSH_FXP_RENAME = 18,
+ SSH_FXP_READLINK = 19,
+ SSH_FXP_SYMLINK = 20, // Removed from later protocol versions. Try not to use.
+
+ SSH_FXP_STATUS = 101,
+ SSH_FXP_HANDLE = 102,
+ SSH_FXP_DATA = 103,
+ SSH_FXP_NAME = 104,
+ SSH_FXP_ATTRS = 105,
+
+ SSH_FXP_EXTENDED = 200,
+ SSH_FXP_EXTENDED_REPLY = 201
+};
+
+enum SftpStatusCode {
+ SSH_FX_OK = 0,
+ SSH_FX_EOF = 1,
+ SSH_FX_NO_SUCH_FILE = 2,
+ SSH_FX_PERMISSION_DENIED = 3,
+ SSH_FX_FAILURE = 4,
+ SSH_FX_BAD_MESSAGE = 5,
+ SSH_FX_NO_CONNECTION = 6,
+ SSH_FX_CONNECTION_LOST = 7,
+ SSH_FX_OP_UNSUPPORTED = 8
+};
+
+enum SftpAttributeType {
+ SSH_FILEXFER_ATTR_SIZE = 0x00000001,
+ SSH_FILEXFER_ATTR_UIDGID = 0x00000002,
+ SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004,
+ SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008,
+ SSH_FILEXFER_ATTR_EXTENDED = 0x80000000
+};
+
+class AbstractSftpPacket
+{
+public:
+ AbstractSftpPacket();
+ quint32 requestId() const;
+ const QByteArray &rawData() const { return m_data; }
+ SftpPacketType type() const { return static_cast<SftpPacketType>(m_data.at(TypeOffset)); }
+
+ static const quint32 MaxDataSize; // "Pure" data size per read/writepacket.
+ static const quint32 MaxPacketSize;
+
+protected:
+ quint32 dataSize() const { return static_cast<quint32>(m_data.size()); }
+
+ static const int TypeOffset;
+ static const int RequestIdOffset;
+ static const int PayloadOffset;
+
+ QByteArray m_data;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SFTPPACKET_P_H
diff --git a/src/libs/ssh/ssh.pri b/src/libs/ssh/ssh.pri
new file mode 100644
index 0000000000..fe58b81cba
--- /dev/null
+++ b/src/libs/ssh/ssh.pri
@@ -0,0 +1,2 @@
+include(ssh_dependencies.pri)
+LIBS *= -l$$qtLibraryName(QtcSsh)
diff --git a/src/libs/ssh/ssh.pro b/src/libs/ssh/ssh.pro
new file mode 100644
index 0000000000..2df5b0430b
--- /dev/null
+++ b/src/libs/ssh/ssh.pro
@@ -0,0 +1,67 @@
+TEMPLATE = lib
+TARGET = QtcSsh
+QT += gui network
+DEFINES += QSSH_LIBRARY
+
+include(../../qtcreatorlibrary.pri)
+include(ssh_dependencies.pri)
+
+SOURCES = $$PWD/sshsendfacility.cpp \
+ $$PWD/sshremoteprocess.cpp \
+ $$PWD/sshpacketparser.cpp \
+ $$PWD/sshpacket.cpp \
+ $$PWD/sshoutgoingpacket.cpp \
+ $$PWD/sshkeygenerator.cpp \
+ $$PWD/sshkeyexchange.cpp \
+ $$PWD/sshincomingpacket.cpp \
+ $$PWD/sshcryptofacility.cpp \
+ $$PWD/sshconnection.cpp \
+ $$PWD/sshchannelmanager.cpp \
+ $$PWD/sshchannel.cpp \
+ $$PWD/sshcapabilities.cpp \
+ $$PWD/sftppacket.cpp \
+ $$PWD/sftpoutgoingpacket.cpp \
+ $$PWD/sftpoperation.cpp \
+ $$PWD/sftpincomingpacket.cpp \
+ $$PWD/sftpdefs.cpp \
+ $$PWD/sftpchannel.cpp \
+ $$PWD/sshremoteprocessrunner.cpp \
+ $$PWD/sshconnectionmanager.cpp \
+ $$PWD/sshkeypasswordretriever.cpp \
+ $$PWD/sftpfilesystemmodel.cpp \
+ $$PWD/sshkeycreationdialog.cpp
+
+HEADERS = $$PWD/sshsendfacility_p.h \
+ $$PWD/sshremoteprocess.h \
+ $$PWD/sshremoteprocess_p.h \
+ $$PWD/sshpacketparser_p.h \
+ $$PWD/sshpacket_p.h \
+ $$PWD/sshoutgoingpacket_p.h \
+ $$PWD/sshkeygenerator.h \
+ $$PWD/sshkeyexchange_p.h \
+ $$PWD/sshincomingpacket_p.h \
+ $$PWD/sshexception_p.h \
+ $$PWD/ssherrors.h \
+ $$PWD/sshcryptofacility_p.h \
+ $$PWD/sshconnection.h \
+ $$PWD/sshconnection_p.h \
+ $$PWD/sshchannelmanager_p.h \
+ $$PWD/sshchannel_p.h \
+ $$PWD/sshcapabilities_p.h \
+ $$PWD/sshbotanconversions_p.h \
+ $$PWD/sftppacket_p.h \
+ $$PWD/sftpoutgoingpacket_p.h \
+ $$PWD/sftpoperation_p.h \
+ $$PWD/sftpincomingpacket_p.h \
+ $$PWD/sftpdefs.h \
+ $$PWD/sftpchannel.h \
+ $$PWD/sftpchannel_p.h \
+ $$PWD/sshremoteprocessrunner.h \
+ $$PWD/sshconnectionmanager.h \
+ $$PWD/sshpseudoterminal.h \
+ $$PWD/sshkeypasswordretriever_p.h \
+ $$PWD/sftpfilesystemmodel.h \
+ $$PWD/sshkeycreationdialog.h \
+ $$PWD/ssh_global.h
+
+FORMS = $$PWD/sshkeycreationdialog.ui
diff --git a/src/libs/ssh/ssh.qbs b/src/libs/ssh/ssh.qbs
new file mode 100644
index 0000000000..ff108d4967
--- /dev/null
+++ b/src/libs/ssh/ssh.qbs
@@ -0,0 +1,51 @@
+import qbs.base 1.0
+import "../QtcLibrary.qbs" as QtcLibrary
+
+QtcLibrary {
+ name: "QtcSsh"
+
+ cpp.defines: ["QSSH_LIBRARY"]
+ cpp.includePaths: [ ".", "..",
+ "../..",
+ "../3rdparty/botan/build",
+ buildDirectory
+ ]
+
+ Depends { name: "cpp" }
+ Depends { name: "Qt"; submodules: ['gui', 'network' ] }
+ Depends { name: "Botan" }
+
+ files: [
+ "sftpchannel.h", "sftpchannel_p.h", "sftpchannel.cpp",
+ "sftpdefs.cpp", "sftpdefs.h",
+ "sftpincomingpacket.cpp", "sftpincomingpacket_p.h",
+ "sftpoperation.cpp", "sftpoperation_p.h",
+ "sftpoutgoingpacket.cpp", "sftpoutgoingpacket_p.h",
+ "sftppacket.cpp", "sftppacket_p.h",
+ "sshcapabilities_p.h", "sshcapabilities.cpp",
+ "sshchannel.cpp", "sshchannel_p.h",
+ "sshchannelmanager.cpp", "sshchannelmanager_p.h",
+ "sshconnection.h", "sshconnection_p.h", "sshconnection.cpp",
+ "sshconnectionmanager.cpp", "sshconnectionmanager.h",
+ "sshcryptofacility.cpp", "sshcryptofacility_p.h",
+ "sshkeyexchange.cpp", "sshkeyexchange_p.h",
+ "sshkeypasswordretriever_p.h",
+ "sshoutgoingpacket.cpp", "sshoutgoingpacket_p.h",
+ "sshpacket.cpp", "sshpacket_p.h",
+ "sshpacketparser.cpp", "sshpacketparser_p.h",
+ "sshremoteprocess.cpp", "sshremoteprocess.h", "sshremoteprocess_p.h",
+ "sshremoteprocessrunner.cpp", "sshremoteprocessrunner.h",
+ "sshsendfacility.cpp", "sshsendfacility_p.h",
+ "sshkeypasswordretriever.cpp",
+ "sshkeygenerator.cpp", "sshkeygenerator.h",
+ "sshkeycreationdialog.cpp", "sshkeycreationdialog.h", "sshkeycreationdialog.ui",
+ "sshincomingpacket_p.h", "sshincomingpacket.cpp",
+ "ssherrors.h",
+ "sshexception_p.h",
+ "sshpseudoterminal.h",
+ "sshbotanconversions_p.h"
+ ]
+
+ ProductModule { Depends { name: "Qt"; submodules: ["widgets", "network"] } }
+}
+
diff --git a/src/libs/ssh/ssh_dependencies.pri b/src/libs/ssh/ssh_dependencies.pri
new file mode 100644
index 0000000000..e2a3c96f45
--- /dev/null
+++ b/src/libs/ssh/ssh_dependencies.pri
@@ -0,0 +1 @@
+include(../3rdparty/botan/botan.pri)
diff --git a/src/libs/ssh/ssh_global.h b/src/libs/ssh/ssh_global.h
new file mode 100644
index 0000000000..ca89d2dcfd
--- /dev/null
+++ b/src/libs/ssh/ssh_global.h
@@ -0,0 +1,49 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSH_GLOBAL_H
+#define SSH_GLOBAL_H
+
+#include <QtGlobal>
+
+#if defined(QSSH_LIBRARY)
+# define QSSH_EXPORT Q_DECL_EXPORT
+#else
+# define QSSH_EXPORT Q_DECL_IMPORT
+#endif
+
+#define QSSH_PRINT_WARNING qWarning("Soft assert at %s:%d", __FILE__, __LINE__)
+#define QSSH_ASSERT(cond) do { if (!(cond)) { QSSH_PRINT_WARNING; } } while (false)
+#define QSSH_ASSERT_AND_RETURN(cond) do { if (!(cond)) { QSSH_PRINT_WARNING; return; } } while (false)
+#define QSSH_ASSERT_AND_RETURN_VALUE(cond, value) do { if (!(cond)) { QSSH_PRINT_WARNING; return value; } } while (false)
+
+#endif // SSH_GLOBAL_H
diff --git a/src/libs/ssh/sshbotanconversions_p.h b/src/libs/ssh/sshbotanconversions_p.h
new file mode 100644
index 0000000000..ea52148693
--- /dev/null
+++ b/src/libs/ssh/sshbotanconversions_p.h
@@ -0,0 +1,102 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef BYTEARRAYCONVERSIONS_P_H
+#define BYTEARRAYCONVERSIONS_P_H
+
+#include "sshcapabilities_p.h"
+
+#include <botan/rng.h>
+#include <botan/secmem.h>
+
+namespace QSsh {
+namespace Internal {
+
+inline const Botan::byte *convertByteArray(const QByteArray &a)
+{
+ return reinterpret_cast<const Botan::byte *>(a.constData());
+}
+
+inline Botan::byte *convertByteArray(QByteArray &a)
+{
+ return reinterpret_cast<Botan::byte *>(a.data());
+}
+
+inline QByteArray convertByteArray(const Botan::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);
+ Q_UNUSED(rfcAlgoName);
+ return botanSha1Name();
+}
+
+inline quint32 botanHMacKeyLen(const QByteArray &rfcAlgoName)
+{
+ Q_ASSERT(rfcAlgoName == SshCapabilities::HMacSha1);
+ Q_UNUSED(rfcAlgoName);
+ return 20;
+}
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // BYTEARRAYCONVERSIONS_P_H
diff --git a/src/libs/ssh/sshcapabilities.cpp b/src/libs/ssh/sshcapabilities.cpp
new file mode 100644
index 0000000000..340fedcf4f
--- /dev/null
+++ b/src/libs/ssh/sshcapabilities.cpp
@@ -0,0 +1,106 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshcapabilities_p.h"
+
+#include "sshexception_p.h"
+
+#include <QCoreApplication>
+#include <QString>
+
+namespace QSsh {
+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 do not match.",
+ QCoreApplication::translate("SshConnection",
+ "Server and client capabilities don't match. "
+ "Client list was: %1.\nServer list was %2.")
+ .arg(QString::fromLocal8Bit(listAsByteArray(myCapabilities).data()))
+ .arg(QString::fromLocal8Bit(listAsByteArray(serverCapabilities).data())));
+}
+
+} // namespace Internal
+} // namespace QSsh
diff --git a/src/libs/ssh/sshcapabilities_p.h b/src/libs/ssh/sshcapabilities_p.h
new file mode 100644
index 0000000000..c36163e6a9
--- /dev/null
+++ b/src/libs/ssh/sshcapabilities_p.h
@@ -0,0 +1,75 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef CAPABILITIES_P_H
+#define CAPABILITIES_P_H
+
+#include <QByteArray>
+#include <QList>
+
+namespace QSsh {
+namespace Internal {
+
+class SshCapabilities
+{
+public:
+ static const QByteArray DiffieHellmanGroup1Sha1;
+ static const QByteArray DiffieHellmanGroup14Sha1;
+ static const 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 QSsh
+
+#endif // CAPABILITIES_P_H
diff --git a/src/libs/ssh/sshchannel.cpp b/src/libs/ssh/sshchannel.cpp
new file mode 100644
index 0000000000..0d8fbfbaae
--- /dev/null
+++ b/src/libs/ssh/sshchannel.cpp
@@ -0,0 +1,263 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshchannel_p.h"
+
+#include "sshincomingpacket_p.h"
+#include "sshsendfacility_p.h"
+
+#include <botan/exceptn.h>
+
+#include <QTimer>
+
+namespace QSsh {
+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_timeoutTimer(new QTimer(this)),
+ m_localChannel(channelId), m_remoteChannel(NoChannel),
+ m_localWindowSize(InitialWindowSize), m_remoteWindowSize(0),
+ m_state(Inactive)
+{
+ m_timeoutTimer->setSingleShot(true);
+ connect(m_timeoutTimer, SIGNAL(timeout()), this, SIGNAL(timeout()));
+}
+
+AbstractSshChannel::~AbstractSshChannel()
+{
+
+}
+
+void AbstractSshChannel::setChannelState(ChannelState state)
+{
+ m_state = state;
+ if (state == Closed)
+ closeHook();
+}
+
+void AbstractSshChannel::requestSessionStart()
+{
+ // Note: We are just being paranoid here about the Botan exceptions,
+ // which are extremely unlikely to happen, because if there was a problem
+ // with our cryptography stuff, it would have hit us before, on
+ // establishing the connection.
+ try {
+ m_sendFacility.sendSessionPacket(m_localChannel, InitialWindowSize,
+ MaxPacketSize);
+ setChannelState(SessionRequested);
+ m_timeoutTimer->start(ReplyTimeout);
+ } catch (Botan::Exception &e) {
+ qDebug("Botan error: %s", e.what());
+ closeChannel();
+ }
+}
+
+void AbstractSshChannel::sendData(const QByteArray &data)
+{
+ try {
+ m_sendBuffer += data;
+ flushSendBuffer();
+ } catch (Botan::Exception &e) {
+ qDebug("Botan error: %s", 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(m_remoteMaxPacketSize,
+ 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.");
+ }
+ m_timeoutTimer->stop();
+
+ 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 - sizeof(quint32) - sizeof m_remoteChannel - 1;
+ // Original value includes packet type, channel number and length field for string.
+ 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.");
+ }
+ m_timeoutTimer->stop();
+
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Channel open request failed for channel %u", m_localChannel);
+#endif
+ handleOpenFailureInternal(reason);
+}
+
+void AbstractSshChannel::handleChannelEof()
+{
+ if (m_state == Inactive || m_state == Closed) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_MSG_CHANNEL_EOF message.");
+ }
+ m_localWindowSize = 0;
+}
+
+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)
+{
+ checkChannelActive();
+ const QByteArray &requestType = packet.extractChannelRequestType();
+ if (requestType == SshIncomingPacket::ExitStatusType)
+ handleExitStatus(packet.extractChannelExitStatus());
+ else if (requestType == SshIncomingPacket::ExitSignalType)
+ handleExitSignal(packet.extractChannelExitSignal());
+ else if (requestType != "eow@openssh.com") // Suppress warning for this one, as it's sent all the time.
+ qWarning("Ignoring unknown request type '%s'", requestType.data());
+}
+
+int AbstractSshChannel::handleChannelOrExtendedChannelData(const QByteArray &data)
+{
+ checkChannelActive();
+
+ const int bytesToDeliver = qMin<quint32>(data.size(), maxDataSize());
+ if (bytesToDeliver != data.size())
+ 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_timeoutTimer->stop();
+ } else if (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 QSsh
diff --git a/src/libs/ssh/sshchannel_p.h b/src/libs/ssh/sshchannel_p.h
new file mode 100644
index 0000000000..70e55d9c8f
--- /dev/null
+++ b/src/libs/ssh/sshchannel_p.h
@@ -0,0 +1,124 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHCHANNEL_P_H
+#define SSHCHANNEL_P_H
+
+#include <QByteArray>
+#include <QObject>
+#include <QString>
+
+QT_FORWARD_DECLARE_CLASS(QTimer)
+
+namespace QSsh {
+namespace Internal {
+
+struct SshChannelExitSignal;
+struct SshChannelExitStatus;
+class SshIncomingPacket;
+class SshSendFacility;
+
+class AbstractSshChannel : public QObject
+{
+ Q_OBJECT
+public:
+ enum ChannelState {
+ Inactive, SessionRequested, SessionEstablished, CloseRequested, Closed
+ };
+
+ ChannelState channelState() const { return m_state; }
+ void setChannelState(ChannelState state);
+
+ quint32 localChannelId() const { return m_localChannel; }
+ quint32 remoteChannel() const { return m_remoteChannel; }
+
+ virtual void handleChannelSuccess() = 0;
+ virtual void handleChannelFailure() = 0;
+
+ 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 handleChannelRequest(const SshIncomingPacket &packet);
+
+ void requestSessionStart();
+ void sendData(const QByteArray &data);
+ void closeChannel();
+
+ virtual ~AbstractSshChannel();
+
+ static const int ReplyTimeout = 10000; // milli seconds
+
+signals:
+ void timeout();
+
+protected:
+ AbstractSshChannel(quint32 channelId, SshSendFacility &sendFacility);
+
+ quint32 maxDataSize() const;
+ void checkChannelActive();
+
+ SshSendFacility &m_sendFacility;
+ QTimer * const m_timeoutTimer;
+
+private:
+ virtual void handleOpenSuccessInternal() = 0;
+ virtual void handleOpenFailureInternal(const QString &reason) = 0;
+ virtual void handleChannelDataInternal(const QByteArray &data) = 0;
+ virtual void handleChannelExtendedDataInternal(quint32 type,
+ const QByteArray &data) = 0;
+ virtual void handleExitStatus(const SshChannelExitStatus &exitStatus) = 0;
+ virtual void handleExitSignal(const SshChannelExitSignal &signal) = 0;
+
+ 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;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHCHANNEL_P_H
diff --git a/src/libs/ssh/sshchannelmanager.cpp b/src/libs/ssh/sshchannelmanager.cpp
new file mode 100644
index 0000000000..409f2a586f
--- /dev/null
+++ b/src/libs/ssh/sshchannelmanager.cpp
@@ -0,0 +1,198 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "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 <QList>
+
+namespace QSsh {
+namespace Internal {
+
+SshChannelManager::SshChannelManager(SshSendFacility &sendFacility,
+ QObject *parent)
+ : QObject(parent), m_sendFacility(sendFacility), m_nextLocalChannelId(0)
+{
+}
+
+void SshChannelManager::handleChannelRequest(const SshIncomingPacket &packet)
+{
+ lookupChannel(packet.extractRecipientChannel())
+ ->handleChannelRequest(packet);
+}
+
+void SshChannelManager::handleChannelOpen(const SshIncomingPacket &)
+{
+ 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.",
+ 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();
+}
+
+QSsh::SshRemoteProcess::Ptr SshChannelManager::createRemoteProcess(const QByteArray &command)
+{
+ SshRemoteProcess::Ptr proc(new SshRemoteProcess(command, m_nextLocalChannelId++, m_sendFacility));
+ insertChannel(proc->d, proc);
+ return proc;
+}
+
+QSsh::SshRemoteProcess::Ptr SshChannelManager::createRemoteShell()
+{
+ SshRemoteProcess::Ptr proc(new SshRemoteProcess(m_nextLocalChannelId++, m_sendFacility));
+ insertChannel(proc->d, proc);
+ return proc;
+}
+
+QSsh::SftpChannel::Ptr SshChannelManager::createSftpChannel()
+{
+ SftpChannel::Ptr sftp(new SftpChannel(m_nextLocalChannelId++, m_sendFacility));
+ insertChannel(sftp->d, sftp);
+ return sftp;
+}
+
+void SshChannelManager::insertChannel(AbstractSshChannel *priv,
+ const QSharedPointer<QObject> &pub)
+{
+ connect(priv, SIGNAL(timeout()), this, SIGNAL(timeout()));
+ 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 QSsh
diff --git a/src/libs/ssh/sshchannelmanager_p.h b/src/libs/ssh/sshchannelmanager_p.h
new file mode 100644
index 0000000000..9cc88e9dd6
--- /dev/null
+++ b/src/libs/ssh/sshchannelmanager_p.h
@@ -0,0 +1,97 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHCHANNELLAYER_P_H
+#define SSHCHANNELLAYER_P_H
+
+#include <QHash>
+#include <QObject>
+#include <QSharedPointer>
+
+namespace QSsh {
+
+class SftpChannel;
+class SshRemoteProcess;
+
+namespace Internal {
+
+class AbstractSshChannel;
+class SshIncomingPacket;
+class SshSendFacility;
+
+class SshChannelManager : public QObject
+{
+ Q_OBJECT
+public:
+ SshChannelManager(SshSendFacility &sendFacility, QObject *parent);
+
+ QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
+ QSharedPointer<SshRemoteProcess> createRemoteShell();
+ 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);
+
+signals:
+ void timeout();
+
+private:
+ typedef QHash<quint32, AbstractSshChannel *>::Iterator ChannelIterator;
+
+ ChannelIterator lookupChannelAsIterator(quint32 channelId,
+ bool allowNotFound = false);
+ AbstractSshChannel *lookupChannel(quint32 channelId,
+ bool allowNotFound = false);
+ void removeChannel(ChannelIterator it);
+ void insertChannel(AbstractSshChannel *priv,
+ const QSharedPointer<QObject> &pub);
+
+ SshSendFacility &m_sendFacility;
+ QHash<quint32, AbstractSshChannel *> m_channels;
+ QHash<AbstractSshChannel *, QSharedPointer<QObject> > m_sessions;
+ quint32 m_nextLocalChannelId;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHCHANNELLAYER_P_H
diff --git a/src/libs/ssh/sshconnection.cpp b/src/libs/ssh/sshconnection.cpp
new file mode 100644
index 0000000000..a9ee712ec5
--- /dev/null
+++ b/src/libs/ssh/sshconnection.cpp
@@ -0,0 +1,728 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshconnection.h"
+#include "sshconnection_p.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 <botan/exceptn.h>
+#include <botan/init.h>
+
+#include <QFile>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QNetworkProxy>
+#include <QTcpSocket>
+
+/*!
+ \class QSsh::SshConnection
+
+ \brief 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.
+*/
+
+namespace QSsh {
+
+namespace {
+ const QByteArray ClientId("SSH-2.0-QtCreator\r\n");
+
+ bool staticInitializationsDone = false;
+ QMutex staticInitMutex;
+
+ void doStaticInitializationsIfNecessary()
+ {
+ QMutexLocker locker(&staticInitMutex);
+ if (!staticInitializationsDone) {
+ Botan::LibraryInitializer::initialize("thread_safe=true");
+ qRegisterMetaType<QSsh::SshError>("QSsh::SshError");
+ qRegisterMetaType<QSsh::SftpJobId>("QSsh::SftpJobId");
+ qRegisterMetaType<QSsh::SftpFileInfo>("QSsh::SftpFileInfo");
+ qRegisterMetaType<QList <QSsh::SftpFileInfo> >("QList<QSsh::SftpFileInfo>");
+ staticInitializationsDone = true;
+ }
+ }
+} // anonymous namespace
+
+
+SshConnectionParameters::SshConnectionParameters() :
+ timeout(0), authenticationType(AuthenticationByKey), port(0), proxyType(NoProxy)
+{
+}
+
+static inline bool equals(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
+{
+ return p1.host == p2.host && p1.userName == p2.userName
+ && p1.authenticationType == p2.authenticationType
+ && (p1.authenticationType == SshConnectionParameters::AuthenticationByPassword ?
+ p1.password == p2.password : p1.privateKeyFile == p2.privateKeyFile)
+ && p1.timeout == p2.timeout && p1.port == p2.port;
+}
+
+bool operator==(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
+{
+ return equals(p1, p2);
+}
+
+bool operator!=(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
+{
+ return !equals(p1, p2);
+}
+
+// TODO: Mechanism for checking the host key. First connection to host: save, later: compare
+
+SshConnection::Ptr SshConnection::create(const SshConnectionParameters &serverInfo)
+{
+ doStaticInitializationsIfNecessary();
+ return Ptr(new SshConnection(serverInfo));
+}
+
+SshConnection::SshConnection(const SshConnectionParameters &serverInfo)
+ : d(new Internal::SshConnectionPrivate(this, serverInfo))
+{
+ 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(QSsh::SshError)), this,
+ SIGNAL(error(QSsh::SshError)), Qt::QueuedConnection);
+}
+
+void SshConnection::connectToHost()
+{
+ d->connectToHost();
+}
+
+void SshConnection::disconnectFromHost()
+{
+ d->closeConnection(Internal::SSH_DISCONNECT_BY_APPLICATION, SshNoError, "",
+ QString());
+}
+
+SshConnection::State SshConnection::state() const
+{
+ switch (d->state()) {
+ case Internal::SocketUnconnected:
+ return Unconnected;
+ case Internal::ConnectionEstablished:
+ return Connected;
+ default:
+ return Connecting;
+ }
+}
+
+SshError SshConnection::errorState() const
+{
+ return d->error();
+}
+
+QString SshConnection::errorString() const
+{
+ return d->errorString();
+}
+
+SshConnectionParameters SshConnection::connectionParameters() const
+{
+ return d->m_connParams;
+}
+
+SshConnectionInfo SshConnection::connectionInfo() const
+{
+ QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshConnectionInfo());
+
+ return SshConnectionInfo(d->m_socket->localAddress(), d->m_socket->localPort(),
+ d->m_socket->peerAddress(), d->m_socket->peerPort());
+}
+
+SshConnection::~SshConnection()
+{
+ disconnect();
+ disconnectFromHost();
+ delete d;
+}
+
+QSharedPointer<SshRemoteProcess> SshConnection::createRemoteProcess(const QByteArray &command)
+{
+ QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SshRemoteProcess>());
+ return d->createRemoteProcess(command);
+}
+
+QSharedPointer<SshRemoteProcess> SshConnection::createRemoteShell()
+{
+ QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SshRemoteProcess>());
+ return d->createRemoteShell();
+}
+
+QSharedPointer<SftpChannel> SshConnection::createSftpChannel()
+{
+ QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SftpChannel>());
+ return d->createSftpChannel();
+}
+
+
+namespace Internal {
+
+SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn,
+ const SshConnectionParameters &serverInfo)
+ : m_socket(new QTcpSocket(this)), m_state(SocketUnconnected),
+ m_sendFacility(m_socket),
+ m_channelManager(new SshChannelManager(m_sendFacility, this)),
+ m_connParams(serverInfo), m_error(SshNoError), m_ignoreNextPacket(false),
+ m_conn(conn)
+{
+ setupPacketHandlers();
+ m_socket->setProxy(m_connParams.proxyType == SshConnectionParameters::DefaultProxy
+ ? QNetworkProxy::DefaultProxy : QNetworkProxy::NoProxy);
+ m_timeoutTimer.setSingleShot(true);
+ m_timeoutTimer.setInterval(m_connParams.timeout * 1000);
+ m_keepAliveTimer.setSingleShot(true);
+ m_keepAliveTimer.setInterval(10000);
+ connect(m_channelManager, SIGNAL(timeout()), this, SLOT(handleTimeout()));
+}
+
+SshConnectionPrivate::~SshConnectionPrivate()
+{
+ disconnect();
+}
+
+void SshConnectionPrivate::setupPacketHandlers()
+{
+ typedef SshConnectionPrivate This;
+
+ setupPacketHandler(SSH_MSG_KEXINIT, StateList() << SocketConnected
+ << ConnectionEstablished, &This::handleKeyExchangeInitPacket);
+ setupPacketHandler(SSH_MSG_KEXDH_REPLY, StateList() << SocketConnected
+ << ConnectionEstablished, &This::handleKeyExchangeReplyPacket);
+
+ setupPacketHandler(SSH_MSG_NEWKEYS, StateList() << SocketConnected
+ << ConnectionEstablished, &This::handleNewKeysPacket);
+ setupPacketHandler(SSH_MSG_SERVICE_ACCEPT,
+ StateList() << UserAuthServiceRequested,
+ &This::handleServiceAcceptPacket);
+ 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
+ << UserAuthServiceRequested << UserAuthRequested
+ << ConnectionEstablished, &This::handleDisconnect);
+
+ setupPacketHandler(SSH_MSG_UNIMPLEMENTED,
+ StateList() << ConnectionEstablished, &This::handleUnimplementedPacket);
+}
+
+void SshConnectionPrivate::setupPacketHandler(SshPacketType type,
+ const SshConnectionPrivate::StateList &states,
+ SshConnectionPrivate::PacketHandler handler)
+{
+ m_packetHandlers.insert(type, HandlerInStates(states, handler));
+}
+
+void SshConnectionPrivate::handleSocketConnected()
+{
+ m_state = SocketConnected;
+ sendData(ClientId);
+}
+
+void SshConnectionPrivate::handleIncomingData()
+{
+ if (m_state == SocketUnconnected)
+ return; // For stuff queued in the event loop after we've called closeConnection();
+
+ try {
+ if (!canUseSocket())
+ return;
+ m_incomingData += m_socket->readAll();
+#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 library exception: %1").arg(QString::fromAscii(e.what())));
+ }
+}
+
+void SshConnectionPrivate::handleServerId()
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("%s: incoming data size = %d, incoming data = '%s'",
+ Q_FUNC_INFO, m_incomingData.count(), m_incomingData.data());
+#endif
+ 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)));
+ }
+
+ m_keyExchange.reset(new SshKeyExchange(m_sendFacility));
+ m_serverId = m_incomingData.left(endOffset);
+ m_keyExchange->sendKexInitPacket(m_serverId);
+ m_keyExchangeState = KexInitSent;
+ m_incomingData.remove(0, endOffset + 2);
+}
+
+void SshConnectionPrivate::handlePackets()
+{
+ m_incomingPacket.consumeData(m_incomingData);
+ while (m_incomingPacket.isComplete()) {
+ handleCurrentPacket();
+ m_incomingPacket.clear();
+ m_incomingPacket.consumeData(m_incomingData);
+ }
+}
+
+void SshConnectionPrivate::handleCurrentPacket()
+{
+ Q_ASSERT(m_incomingPacket.isComplete());
+ Q_ASSERT(m_keyExchangeState == DhInitSent || !m_ignoreNextPacket);
+
+ if (m_ignoreNextPacket) {
+ m_ignoreNextPacket = false;
+ return;
+ }
+
+ QHash<SshPacketType, HandlerInStates>::ConstIterator it
+ = m_packetHandlers.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)();
+}
+
+void SshConnectionPrivate::handleKeyExchangeInitPacket()
+{
+ if (m_keyExchangeState != NoKeyExchange
+ && m_keyExchangeState != KexInitSent) {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected packet.", tr("Unexpected packet of type %1.")
+ .arg(m_incomingPacket.type()));
+ }
+
+ // Server-initiated re-exchange.
+ if (m_keyExchangeState == NoKeyExchange) {
+ m_keyExchange.reset(new SshKeyExchange(m_sendFacility));
+ m_keyExchange->sendKexInitPacket(m_serverId);
+ }
+
+ // If the server sends a guessed packet, the guess must be wrong,
+ // because the algorithms we support require us to initiate the
+ // key exchange.
+ if (m_keyExchange->sendDhInitPacket(m_incomingPacket)) {
+ m_ignoreNextPacket = true;
+ }
+
+ m_keyExchangeState = DhInitSent;
+}
+
+void SshConnectionPrivate::handleKeyExchangeReplyPacket()
+{
+ if (m_keyExchangeState != DhInitSent) {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected packet.", tr("Unexpected packet of type %1.")
+ .arg(m_incomingPacket.type()));
+ }
+
+ m_keyExchange->sendNewKeysPacket(m_incomingPacket,
+ ClientId.left(ClientId.size() - 2));
+ m_sendFacility.recreateKeys(*m_keyExchange);
+ m_keyExchangeState = NewKeysSent;
+}
+
+void SshConnectionPrivate::handleNewKeysPacket()
+{
+ if (m_keyExchangeState != NewKeysSent) {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected packet.", tr("Unexpected packet of type %1.")
+ .arg(m_incomingPacket.type()));
+ }
+
+ m_incomingPacket.recreateKeys(*m_keyExchange);
+ m_keyExchange.reset();
+ m_keyExchangeState = NoKeyExchange;
+
+ if (m_state == SocketConnected) {
+ m_sendFacility.sendUserAuthServiceRequestPacket();
+ m_state = UserAuthServiceRequested;
+ }
+}
+
+void SshConnectionPrivate::handleServiceAcceptPacket()
+{
+ if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByPassword) {
+ m_sendFacility.sendUserAuthByPwdRequestPacket(m_connParams.userName.toUtf8(),
+ SshCapabilities::SshConnectionService, m_connParams.password.toUtf8());
+ } else {
+ m_sendFacility.sendUserAuthByKeyRequestPacket(m_connParams.userName.toUtf8(),
+ SshCapabilities::SshConnectionService);
+ }
+ m_state = UserAuthRequested;
+}
+
+void SshConnectionPrivate::handlePasswordExpiredPacket()
+{
+ if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByKey) {
+ 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."));
+}
+
+void SshConnectionPrivate::handleUserAuthBannerPacket()
+{
+ emit dataAvailable(m_incomingPacket.extractUserAuthBanner().message);
+}
+
+void SshConnectionPrivate::handleGlobalRequest()
+{
+ m_sendFacility.sendRequestFailurePacket();
+}
+
+void SshConnectionPrivate::handleUserAuthSuccessPacket()
+{
+ m_state = ConnectionEstablished;
+ m_timeoutTimer.stop();
+ emit connected();
+ m_lastInvalidMsgSeqNr = InvalidSeqNr;
+ connect(&m_keepAliveTimer, SIGNAL(timeout()), SLOT(sendKeepAlivePacket()));
+ m_keepAliveTimer.start();
+}
+
+void SshConnectionPrivate::handleUserAuthFailurePacket()
+{
+ m_timeoutTimer.stop();
+ const QString errorMsg = m_connParams.authenticationType == SshConnectionParameters::AuthenticationByPassword
+ ? 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);
+}
+
+void SshConnectionPrivate::handleUnimplementedPacket()
+{
+ const SshUnimplemented &msg = m_incomingPacket.extractUnimplemented();
+ if (msg.invalidMsgSeqNr != m_lastInvalidMsgSeqNr) {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected packet", tr("The server sent an unexpected SSH packet "
+ "of type SSH_MSG_UNIMPLEMENTED."));
+ }
+ m_lastInvalidMsgSeqNr = InvalidSeqNr;
+ m_timeoutTimer.stop();
+ m_keepAliveTimer.start();
+}
+
+void SshConnectionPrivate::handleChannelRequest()
+{
+ m_channelManager->handleChannelRequest(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelOpen()
+{
+ m_channelManager->handleChannelOpen(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelOpenFailure()
+{
+ m_channelManager->handleChannelOpenFailure(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelOpenConfirmation()
+{
+ m_channelManager->handleChannelOpenConfirmation(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelSuccess()
+{
+ m_channelManager->handleChannelSuccess(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelFailure()
+{
+ m_channelManager->handleChannelFailure(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelWindowAdjust()
+{
+ m_channelManager->handleChannelWindowAdjust(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelData()
+{
+ m_channelManager->handleChannelData(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelExtendedData()
+{
+ m_channelManager->handleChannelExtendedData(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelEof()
+{
+ m_channelManager->handleChannelEof(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleChannelClose()
+{
+ m_channelManager->handleChannelClose(m_incomingPacket);
+}
+
+void SshConnectionPrivate::handleDisconnect()
+{
+ const SshDisconnect msg = m_incomingPacket.extractDisconnect();
+ throw SshServerException(SSH_DISCONNECT_CONNECTION_LOST,
+ "", tr("Server closed connection: %1").arg(msg.description));
+}
+
+
+
+void SshConnectionPrivate::sendData(const QByteArray &data)
+{
+ if (canUseSocket())
+ m_socket->write(data);
+}
+
+void SshConnectionPrivate::handleSocketDisconnected()
+{
+ closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError,
+ "Connection closed unexpectedly.",
+ tr("Connection closed unexpectedly."));
+}
+
+void SshConnectionPrivate::handleSocketError()
+{
+ if (m_error == SshNoError) {
+ closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshSocketError,
+ "Network error", m_socket->errorString());
+ }
+}
+
+void SshConnectionPrivate::handleTimeout()
+{
+ closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "",
+ tr("Timeout waiting for reply from server."));
+}
+
+void SshConnectionPrivate::sendKeepAlivePacket()
+{
+ // This type of message is not allowed during key exchange.
+ if (m_keyExchangeState != NoKeyExchange) {
+ m_keepAliveTimer.start();
+ return;
+ }
+
+ Q_ASSERT(m_lastInvalidMsgSeqNr == InvalidSeqNr);
+ m_lastInvalidMsgSeqNr = m_sendFacility.nextClientSeqNr();
+ m_sendFacility.sendInvalidPacket();
+ m_timeoutTimer.start();
+}
+
+void SshConnectionPrivate::connectToHost()
+{
+ QSSH_ASSERT_AND_RETURN(m_state == SocketUnconnected);
+
+ m_incomingData.clear();
+ m_incomingPacket.reset();
+ m_sendFacility.reset();
+ m_error = SshNoError;
+ m_ignoreNextPacket = false;
+ m_errorString.clear();
+
+ try {
+ if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByKey)
+ createPrivateKey();
+ } catch (const SshClientException &ex) {
+ m_error = ex.error;
+ m_errorString = ex.errorString;
+ emit error(m_error);
+ return;
+ }
+
+ 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()));
+ connect(&m_timeoutTimer, SIGNAL(timeout()), this, SLOT(handleTimeout()));
+ m_state = SocketConnecting;
+ m_keyExchangeState = NoKeyExchange;
+ m_timeoutTimer.start();
+ m_socket->connectToHost(m_connParams.host, m_connParams.port);
+}
+
+void SshConnectionPrivate::closeConnection(SshErrorCode sshError,
+ SshError userError, const QByteArray &serverErrorString,
+ const QString &userErrorString)
+{
+ // Prevent endless loops by recursive exceptions.
+ if (m_state == SocketUnconnected || m_error != SshNoError)
+ return;
+
+ m_error = userError;
+ m_errorString = userErrorString;
+ m_timeoutTimer.stop();
+ disconnect(m_socket, 0, this, 0);
+ disconnect(&m_timeoutTimer, 0, this, 0);
+ m_keepAliveTimer.stop();
+ disconnect(&m_keepAliveTimer, 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();
+ if (canUseSocket())
+ m_socket->disconnectFromHost();
+ m_state = SocketUnconnected;
+}
+
+bool SshConnectionPrivate::canUseSocket() const
+{
+ return m_socket->isValid()
+ && m_socket->state() == QAbstractSocket::ConnectedState;
+}
+
+void SshConnectionPrivate::createPrivateKey()
+{
+ if (m_connParams.privateKeyFile.isEmpty())
+ throw SshClientException(SshKeyFileError, tr("No private key file given."));
+ QFile keyFile(m_connParams.privateKeyFile);
+ if (!keyFile.open(QIODevice::ReadOnly)) {
+ throw SshClientException(SshKeyFileError,
+ tr("Private key file error: %1").arg(keyFile.errorString()));
+ }
+ m_sendFacility.createAuthenticationKey(keyFile.readAll());
+}
+
+QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)
+{
+ return m_channelManager->createRemoteProcess(command);
+}
+
+QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteShell()
+{
+ return m_channelManager->createRemoteShell();
+}
+
+QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
+{
+ return m_channelManager->createSftpChannel();
+}
+
+const quint64 SshConnectionPrivate::InvalidSeqNr = static_cast<quint64>(-1);
+
+} // namespace Internal
+} // namespace QSsh
diff --git a/src/libs/ssh/sshconnection.h b/src/libs/ssh/sshconnection.h
new file mode 100644
index 0000000000..b631c3e707
--- /dev/null
+++ b/src/libs/ssh/sshconnection.h
@@ -0,0 +1,124 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHCONNECTION_H
+#define SSHCONNECTION_H
+
+#include "ssherrors.h"
+
+#include "ssh_global.h"
+
+#include <QByteArray>
+#include <QObject>
+#include <QSharedPointer>
+#include <QString>
+#include <QHostAddress>
+
+namespace QSsh {
+class SftpChannel;
+class SshRemoteProcess;
+
+namespace Internal {
+class SshConnectionPrivate;
+} // namespace Internal
+
+class QSSH_EXPORT SshConnectionParameters
+{
+public:
+ enum ProxyType { DefaultProxy, NoProxy };
+ enum AuthenticationType { AuthenticationByPassword, AuthenticationByKey };
+ SshConnectionParameters();
+
+ QString host;
+ QString userName;
+ QString password;
+ QString privateKeyFile;
+ int timeout; // In seconds.
+ AuthenticationType authenticationType;
+ quint16 port;
+ ProxyType proxyType;
+};
+
+QSSH_EXPORT bool operator==(const SshConnectionParameters &p1, const SshConnectionParameters &p2);
+QSSH_EXPORT bool operator!=(const SshConnectionParameters &p1, const SshConnectionParameters &p2);
+
+class QSSH_EXPORT SshConnectionInfo
+{
+public:
+ SshConnectionInfo() : localPort(0), peerPort(0) {}
+ SshConnectionInfo(const QHostAddress &la, quint16 lp, const QHostAddress &pa, quint16 pp)
+ : localAddress(la), localPort(lp), peerAddress(pa), peerPort(pp) {}
+
+ QHostAddress localAddress;
+ quint16 localPort;
+ QHostAddress peerAddress;
+ quint16 peerPort;
+};
+
+class QSSH_EXPORT SshConnection : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum State { Unconnected, Connecting, Connected };
+ typedef QSharedPointer<SshConnection> Ptr;
+
+ static Ptr create(const SshConnectionParameters &serverInfo);
+
+ void connectToHost();
+ void disconnectFromHost();
+ State state() const;
+ SshError errorState() const;
+ QString errorString() const;
+ SshConnectionParameters connectionParameters() const;
+ SshConnectionInfo connectionInfo() const;
+ ~SshConnection();
+
+ QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
+ QSharedPointer<SshRemoteProcess> createRemoteShell();
+ QSharedPointer<SftpChannel> createSftpChannel();
+
+signals:
+ void connected();
+ void disconnected();
+ void dataAvailable(const QString &message);
+ void error(QSsh::SshError);
+
+private:
+ SshConnection(const SshConnectionParameters &serverInfo);
+
+ Internal::SshConnectionPrivate *d;
+};
+
+} // namespace QSsh
+
+#endif // SSHCONNECTION_H
diff --git a/src/libs/ssh/sshconnection_p.h b/src/libs/ssh/sshconnection_p.h
new file mode 100644
index 0000000000..b1765e0ec5
--- /dev/null
+++ b/src/libs/ssh/sshconnection_p.h
@@ -0,0 +1,177 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef 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 <QHash>
+#include <QList>
+#include <QObject>
+#include <QPair>
+#include <QScopedPointer>
+#include <QTimer>
+
+QT_BEGIN_NAMESPACE
+class QTcpSocket;
+QT_END_NAMESPACE
+
+namespace Botan { class Exception; }
+
+namespace QSsh {
+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
+ UserAuthServiceRequested,
+ UserAuthRequested,
+ ConnectionEstablished // After service has been started
+ // ...
+};
+
+enum SshKeyExchangeState {
+ NoKeyExchange,
+ KexInitSent,
+ DhInitSent,
+ NewKeysSent,
+ KeyExchangeSuccess // After server's DH_REPLY message
+};
+
+class SshConnectionPrivate : public QObject
+{
+ Q_OBJECT
+ friend class QSsh::SshConnection;
+public:
+ SshConnectionPrivate(SshConnection *conn,
+ const SshConnectionParameters &serverInfo);
+ ~SshConnectionPrivate();
+
+ void connectToHost();
+ void closeConnection(SshErrorCode sshError, SshError userError,
+ const QByteArray &serverErrorString, const QString &userErrorString);
+ QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
+ QSharedPointer<SshRemoteProcess> createRemoteShell();
+ QSharedPointer<SftpChannel> createSftpChannel();
+ 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(QSsh::SshError);
+
+private:
+ Q_SLOT void handleSocketConnected();
+ Q_SLOT void handleIncomingData();
+ Q_SLOT void handleSocketError();
+ Q_SLOT void handleSocketDisconnected();
+ Q_SLOT void handleTimeout();
+ Q_SLOT void sendKeepAlivePacket();
+
+ 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 handleUnimplementedPacket();
+ void handleChannelRequest();
+ void handleChannelOpen();
+ void handleChannelOpenFailure();
+ void handleChannelOpenConfirmation();
+ void handleChannelSuccess();
+ void handleChannelFailure();
+ void handleChannelWindowAdjust();
+ void handleChannelData();
+ void handleChannelExtendedData();
+ void handleChannelEof();
+ void handleChannelClose();
+ void handleDisconnect();
+ bool canUseSocket() const;
+ void createPrivateKey();
+
+ 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;
+
+ static const quint64 InvalidSeqNr;
+
+ QTcpSocket *m_socket;
+ SshStateInternal m_state;
+ SshKeyExchangeState m_keyExchangeState;
+ SshIncomingPacket m_incomingPacket;
+ SshSendFacility m_sendFacility;
+ SshChannelManager * const m_channelManager;
+ const SshConnectionParameters m_connParams;
+ QByteArray m_incomingData;
+ SshError m_error;
+ QString m_errorString;
+ QScopedPointer<SshKeyExchange> m_keyExchange;
+ QTimer m_timeoutTimer;
+ QTimer m_keepAliveTimer;
+ bool m_ignoreNextPacket;
+ SshConnection *m_conn;
+ quint64 m_lastInvalidMsgSeqNr;
+ QByteArray m_serverId;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHCONNECTION_P_H
diff --git a/src/libs/ssh/sshconnectionmanager.cpp b/src/libs/ssh/sshconnectionmanager.cpp
new file mode 100644
index 0000000000..2f5167a048
--- /dev/null
+++ b/src/libs/ssh/sshconnectionmanager.cpp
@@ -0,0 +1,227 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshconnectionmanager.h"
+
+#include "sshconnection.h"
+
+#include <QCoreApplication>
+#include <QList>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QObject>
+#include <QThread>
+
+namespace QSsh {
+namespace Internal {
+
+class SshConnectionManagerPrivate : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ static QMutex instanceMutex;
+ static SshConnectionManager &instance()
+ {
+ static SshConnectionManager manager;
+ return manager;
+ }
+
+ SshConnectionManagerPrivate()
+ {
+ moveToThread(QCoreApplication::instance()->thread());
+ }
+
+ QSharedPointer<SshConnection> acquireConnection(const SshConnectionParameters &sshParams)
+ {
+ QMutexLocker locker(&m_listMutex);
+
+ // Check in-use connections:
+ foreach (SshConnection::Ptr connection, m_acquiredConnections) {
+ if (connection->connectionParameters() != sshParams)
+ continue;
+
+ if (connection->thread() != QThread::currentThread())
+ break;
+
+ if (m_deprecatedConnections.contains(connection)) // we were asked to no longer use this one...
+ break;
+
+ m_acquiredConnections.append(connection);
+ return connection;
+ }
+
+ // Checked cached open connections:
+ foreach (SshConnection::Ptr connection, m_unacquiredConnections) {
+ if (connection->state() != SshConnection::Connected
+ || connection->connectionParameters() != sshParams)
+ continue;
+
+ if (connection->thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "switchToCallerThread",
+ Qt::BlockingQueuedConnection,
+ Q_ARG(SshConnection *, connection.data()),
+ Q_ARG(QObject *, QThread::currentThread()));
+ }
+
+ m_unacquiredConnections.removeOne(connection);
+ m_acquiredConnections.append(connection);
+ return connection;
+ }
+
+ // create a new connection:
+ SshConnection::Ptr connection = SshConnection::create(sshParams);
+ connect(connection.data(), SIGNAL(disconnected()), this, SLOT(cleanup()));
+ m_acquiredConnections.append(connection);
+
+ return connection;
+ }
+
+ void releaseConnection(const SshConnection::Ptr &connection)
+ {
+ QMutexLocker locker(&m_listMutex);
+
+ m_acquiredConnections.removeOne(connection);
+ if (!m_acquiredConnections.contains(connection)) {
+ // no longer in use:
+ connection->moveToThread(QCoreApplication::instance()->thread());
+ if (m_deprecatedConnections.contains(connection))
+ m_deprecatedConnections.removeAll(connection);
+ else if (connection->state() == SshConnection::Connected) {
+ // Make sure to only keep one connection open
+ bool haveConnection = false;
+ foreach (SshConnection::Ptr conn, m_unacquiredConnections) {
+ if (conn->connectionParameters() == connection->connectionParameters()) {
+ haveConnection = true;
+ break;
+ }
+ }
+ if (!haveConnection)
+ m_unacquiredConnections.append(connection);
+ }
+ }
+ }
+
+ void forceNewConnection(const SshConnectionParameters &sshParams)
+ {
+ QMutexLocker locker(&m_listMutex);
+
+ SshConnection::Ptr toReset;
+ foreach (SshConnection::Ptr connection, m_unacquiredConnections) {
+ if (connection->connectionParameters() == sshParams) {
+ toReset = connection;
+ break;
+ }
+ }
+
+ if (toReset.isNull()) {
+ foreach (SshConnection::Ptr connection, m_acquiredConnections) {
+ if (connection->connectionParameters() == sshParams) {
+ toReset = connection;
+ break;
+ }
+ }
+ }
+
+ if (!toReset.isNull() && !m_deprecatedConnections.contains(toReset))
+ m_deprecatedConnections.append(toReset);
+ }
+
+private:
+ Q_INVOKABLE void switchToCallerThread(SshConnection *connection, QObject *threadObj)
+ {
+ connection->moveToThread(qobject_cast<QThread *>(threadObj));
+ }
+
+private slots:
+ void cleanup()
+ {
+ QMutexLocker locker(&m_listMutex);
+
+ SshConnection *currentConnection = qobject_cast<SshConnection *>(sender());
+ if (!currentConnection)
+ return;
+
+ for (int i = m_unacquiredConnections.count() - 1; i >= 0; --i) {
+ if (m_unacquiredConnections.at(i) == currentConnection)
+ m_unacquiredConnections.removeAt(i);
+ }
+ }
+
+private:
+ // We expect the number of concurrently open connections to be small.
+ // If that turns out to not be the case, we can still use a data
+ // structure with faster access.
+ QList<SshConnection::Ptr> m_unacquiredConnections;
+ QList<SshConnection::Ptr> m_acquiredConnections;
+ QList<SshConnection::Ptr> m_deprecatedConnections;
+ QMutex m_listMutex;
+};
+
+QMutex SshConnectionManagerPrivate::instanceMutex;
+
+} // namespace Internal
+
+SshConnectionManager &SshConnectionManager::instance()
+{
+ QMutexLocker locker(&Internal::SshConnectionManagerPrivate::instanceMutex);
+ return Internal::SshConnectionManagerPrivate::instance();
+}
+
+SshConnectionManager::SshConnectionManager()
+ : d(new Internal::SshConnectionManagerPrivate)
+{
+}
+
+SshConnectionManager::~SshConnectionManager()
+{
+}
+
+SshConnection::Ptr SshConnectionManager::acquireConnection(const SshConnectionParameters &sshParams)
+{
+ return d->acquireConnection(sshParams);
+}
+
+void SshConnectionManager::releaseConnection(const SshConnection::Ptr &connection)
+{
+ d->releaseConnection(connection);
+}
+
+void SshConnectionManager::forceNewConnection(const SshConnectionParameters &sshParams)
+{
+ d->forceNewConnection(sshParams);
+}
+
+} // namespace QSsh
+
+#include "sshconnectionmanager.moc"
diff --git a/src/libs/ssh/sshconnectionmanager.h b/src/libs/ssh/sshconnectionmanager.h
new file mode 100644
index 0000000000..f6f7c39228
--- /dev/null
+++ b/src/libs/ssh/sshconnectionmanager.h
@@ -0,0 +1,68 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHCONNECTIONMANAGER_H
+#define SSHCONNECTIONMANAGER_H
+
+#include "ssh_global.h"
+
+#include <QScopedPointer>
+#include <QSharedPointer>
+
+namespace QSsh {
+class SshConnection;
+class SshConnectionParameters;
+namespace Internal { class SshConnectionManagerPrivate; }
+
+class QSSH_EXPORT SshConnectionManager
+{
+ friend class Internal::SshConnectionManagerPrivate;
+public:
+ static SshConnectionManager &instance();
+
+ QSharedPointer<SshConnection> acquireConnection(const SshConnectionParameters &sshParams);
+ void releaseConnection(const QSharedPointer<SshConnection> &connection);
+ // Make sure the next acquireConnection with the given parameters will return a new connection.
+ void forceNewConnection(const SshConnectionParameters &sshParams);
+
+private:
+ explicit SshConnectionManager();
+ virtual ~SshConnectionManager();
+ SshConnectionManager(const SshConnectionManager &);
+ SshConnectionManager &operator=(const SshConnectionManager &);
+
+ const QScopedPointer<Internal::SshConnectionManagerPrivate> d;
+};
+
+} // namespace QSsh
+
+#endif // SSHCONNECTIONMANAGER_H
diff --git a/src/libs/ssh/sshcryptofacility.cpp b/src/libs/ssh/sshcryptofacility.cpp
new file mode 100644
index 0000000000..a795a6f56e
--- /dev/null
+++ b/src/libs/ssh/sshcryptofacility.cpp
@@ -0,0 +1,387 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshcryptofacility_p.h"
+
+#include "sshbotanconversions_p.h"
+#include "sshcapabilities_p.h"
+#include "sshexception_p.h"
+#include "sshkeyexchange_p.h"
+#include "sshkeypasswordretriever_p.h"
+#include "sshpacket_p.h"
+
+#include <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 <QDebug>
+#include <QList>
+
+#include <string>
+
+using namespace Botan;
+
+namespace QSsh {
+namespace Internal {
+
+SshAbstractCryptoFacility::SshAbstractCryptoFacility()
+ : m_cipherBlockSize(0), m_macLength(0)
+{
+}
+
+SshAbstractCryptoFacility::~SshAbstractCryptoFacility() {}
+
+void SshAbstractCryptoFacility::clearKeys()
+{
+ m_cipherBlockSize = 0;
+ m_macLength = 0;
+ m_sessionId.clear();
+ m_pipe.reset(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;
+ QString error1;
+ QString error2;
+ if (!createAuthenticationKeyFromPKCS8(privKeyFileContents, pubKeyParams, allKeyParams, error1)
+ && !createAuthenticationKeyFromOpenSSL(privKeyFileContents, pubKeyParams, allKeyParams,
+ error2)) {
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("%s: %s\n\t%s\n", Q_FUNC_INFO, qPrintable(error1), qPrintable(error2));
+#endif
+ throw SshClientException(SshKeyFileError, SSH_TR("Decoding of private key file failed: "
+ "Format not understood."));
+ }
+
+ foreach (const BigInt &b, allKeyParams) {
+ if (b.is_zero()) {
+ throw SshClientException(SshKeyFileError,
+ SSH_TR("Decoding of private key file failed: Invalid zero parameter."));
+ }
+ }
+
+ m_authPubKeyBlob = AbstractSshPacket::encodeString(m_authKeyAlgoName);
+ foreach (const BigInt &b, pubKeyParams)
+ m_authPubKeyBlob += AbstractSshPacket::encodeMpInt(b);
+ m_cachedPrivKeyContents = privKeyFileContents;
+}
+
+bool SshEncryptionFacility::createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
+ QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
+{
+ try {
+ Pipe pipe;
+ pipe.process_msg(convertByteArray(privKeyFileContents), privKeyFileContents.size());
+ Private_Key * const key = PKCS8::load_key(pipe, m_rng, SshKeyPasswordRetriever());
+ if (DSA_PrivateKey * const dsaKey = dynamic_cast<DSA_PrivateKey *>(key)) {
+ m_authKeyAlgoName = SshCapabilities::PubKeyDss;
+ 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_authKeyAlgoName = SshCapabilities::PubKeyRsa;
+ m_authKey.reset(rsaKey);
+ pubKeyParams << rsaKey->get_e() << rsaKey->get_n();
+ allKeyParams << pubKeyParams << rsaKey->get_p() << rsaKey->get_q()
+ << rsaKey->get_d();
+ } else {
+ qWarning("%s: Unexpected code flow, expected success or exception.", Q_FUNC_INFO);
+ return false;
+ }
+ } catch (const Botan::Exception &ex) {
+ error = QLatin1String(ex.what());
+ return false;
+ }
+ return true;
+}
+
+bool SshEncryptionFacility::createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
+ QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
+{
+ try {
+ bool syntaxOk = true;
+ QList<QByteArray> lines = privKeyFileContents.split('\n');
+ while (lines.last().isEmpty())
+ lines.removeLast();
+ if (lines.count() < 3) {
+ syntaxOk = false;
+ } else if (lines.first() == PrivKeyFileStartLineRsa) {
+ if (lines.last() != PrivKeyFileEndLineRsa)
+ syntaxOk = false;
+ else
+ m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
+ } else if (lines.first() == PrivKeyFileStartLineDsa) {
+ if (lines.last() != PrivKeyFileEndLineDsa)
+ syntaxOk = false;
+ else
+ m_authKeyAlgoName = SshCapabilities::PubKeyDss;
+ } else {
+ syntaxOk = false;
+ }
+ if (!syntaxOk) {
+ error = SSH_TR("Unexpected format.");
+ return false;
+ }
+
+ QByteArray privateKeyBlob;
+ for (int i = 1; i < lines.size() - 1; ++i)
+ privateKeyBlob += lines.at(i);
+ privateKeyBlob = QByteArray::fromBase64(privateKeyBlob);
+
+ BER_Decoder decoder(convertByteArray(privateKeyBlob), privateKeyBlob.size());
+ BER_Decoder sequence = decoder.start_cons(SEQUENCE);
+ quint32 version;
+ sequence.decode (version);
+ if (version != 0) {
+ error = SSH_TR("Key encoding has version %1, expected 0.").arg(version);
+ return false;
+ }
+
+ if (m_authKeyAlgoName == SshCapabilities::PubKeyDss) {
+ BigInt p, q, g, y, x;
+ sequence.decode (p).decode (q).decode (g).decode (y).decode (x);
+ DSA_PrivateKey * const dsaKey = new DSA_PrivateKey(m_rng, DL_Group(p, q, g), x);
+ m_authKey.reset(dsaKey);
+ pubKeyParams << p << q << g << y;
+ allKeyParams << pubKeyParams << x;
+ } else {
+ 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();
+ } catch (const Botan::Exception &ex) {
+ error = QLatin1String(ex.what());
+ return false;
+ }
+ return true;
+}
+
+QByteArray SshEncryptionFacility::authenticationAlgorithmName() const
+{
+ Q_ASSERT(m_authKey);
+ return m_authKeyAlgoName;
+}
+
+QByteArray SshEncryptionFacility::authenticationKeySignature(const QByteArray &data) const
+{
+ Q_ASSERT(m_authKey);
+
+ QScopedPointer<PK_Signer> signer(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 QSsh
diff --git a/src/libs/ssh/sshcryptofacility_p.h b/src/libs/ssh/sshcryptofacility_p.h
new file mode 100644
index 0000000000..6784f6b05b
--- /dev/null
+++ b/src/libs/ssh/sshcryptofacility_p.h
@@ -0,0 +1,157 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHABSTRACTCRYPTOFACILITY_P_H
+#define SSHABSTRACTCRYPTOFACILITY_P_H
+
+#include <botan/auto_rng.h>
+#include <botan/symkey.h>
+
+#include <QByteArray>
+#include <QScopedPointer>
+
+namespace Botan {
+ class BigInt;
+ class BlockCipher;
+ class BlockCipherMode;
+ class BlockCipherModePaddingMethod;
+ class HashFunction;
+ class HMAC;
+ class Pipe;
+ class PK_Signing_Key;
+}
+
+namespace QSsh {
+namespace Internal {
+
+class SshKeyExchange;
+
+class SshAbstractCryptoFacility
+{
+public:
+ virtual ~SshAbstractCryptoFacility();
+
+ void clearKeys();
+ void recreateKeys(const SshKeyExchange &kex);
+ QByteArray generateMac(const QByteArray &data, quint32 dataSize) const;
+ quint32 cipherBlockSize() const { return m_cipherBlockSize; }
+ quint32 macLength() const { return m_macLength; }
+
+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'; }
+
+ bool createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
+ QList<Botan::BigInt> &pubKeyParams, QList<Botan::BigInt> &allKeyParams, QString &error);
+ bool createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
+ QList<Botan::BigInt> &pubKeyParams, QList<Botan::BigInt> &allKeyParams, QString &error);
+
+ static const QByteArray PrivKeyFileStartLineRsa;
+ static const QByteArray PrivKeyFileStartLineDsa;
+ static const QByteArray PrivKeyFileEndLineRsa;
+ static const QByteArray PrivKeyFileEndLineDsa;
+
+ 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 QSsh
+
+#endif // SSHABSTRACTCRYPTOFACILITY_P_H
diff --git a/src/libs/ssh/ssherrors.h b/src/libs/ssh/ssherrors.h
new file mode 100644
index 0000000000..d4ea7f82c4
--- /dev/null
+++ b/src/libs/ssh/ssherrors.h
@@ -0,0 +1,46 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHERRORS_P_H
+#define SSHERRORS_P_H
+
+namespace QSsh {
+
+enum SshError {
+ SshNoError, SshSocketError, SshTimeoutError, SshProtocolError,
+ SshHostKeyError, SshKeyFileError, SshAuthenticationError,
+ SshClosedByServerError, SshInternalError
+};
+
+} // namespace QSsh
+
+#endif // SSHERRORS_P_H
diff --git a/src/libs/ssh/sshexception_p.h b/src/libs/ssh/sshexception_p.h
new file mode 100644
index 0000000000..4cfdfcd9c2
--- /dev/null
+++ b/src/libs/ssh/sshexception_p.h
@@ -0,0 +1,92 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHEXCEPTION_P_H
+#define SSHEXCEPTION_P_H
+
+#include "ssherrors.h"
+
+#include <QByteArray>
+#include <QCoreApplication>
+#include <QString>
+
+namespace QSsh {
+namespace Internal {
+
+enum SshErrorCode {
+ SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1,
+ SSH_DISCONNECT_PROTOCOL_ERROR = 2,
+ SSH_DISCONNECT_KEY_EXCHANGE_FAILED = 3,
+ SSH_DISCONNECT_RESERVED = 4,
+ SSH_DISCONNECT_MAC_ERROR = 5,
+ SSH_DISCONNECT_COMPRESSION_ERROR = 6,
+ SSH_DISCONNECT_SERVICE_NOT_AVAILABLE = 7,
+ SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8,
+ SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9,
+ SSH_DISCONNECT_CONNECTION_LOST = 10,
+ SSH_DISCONNECT_BY_APPLICATION = 11,
+ SSH_DISCONNECT_TOO_MANY_CONNECTIONS = 12,
+ SSH_DISCONNECT_AUTH_CANCELLED_BY_USER = 13,
+ SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14,
+ SSH_DISCONNECT_ILLEGAL_USER_NAME = 15
+};
+
+#define SSH_TR(string) QCoreApplication::translate("SshConnection", string)
+
+#define SSH_SERVER_EXCEPTION(error, errorString) \
+ SshServerException((error), (errorString), SSH_TR(errorString))
+
+struct SshServerException
+{
+ 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 QSsh
+
+#endif // SSHEXCEPTION_P_H
diff --git a/src/libs/ssh/sshincomingpacket.cpp b/src/libs/ssh/sshincomingpacket.cpp
new file mode 100644
index 0000000000..22db307404
--- /dev/null
+++ b/src/libs/ssh/sshincomingpacket.cpp
@@ -0,0 +1,463 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshincomingpacket_p.h"
+
+#include "sshcapabilities_p.h"
+
+namespace QSsh {
+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_DEBUG.");
+ }
+}
+
+SshUnimplemented SshIncomingPacket::extractUnimplemented() const
+{
+ Q_ASSERT(isComplete());
+ Q_ASSERT(type() == SSH_MSG_UNIMPLEMENTED);
+
+ try {
+ SshUnimplemented msg;
+ quint32 offset = TypeOffset + 1;
+ msg.invalidMsgSeqNr = SshPacketParser::asUint32(m_data, &offset);
+ return msg;
+ } catch (SshPacketParseException &) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Invalid SSH_MSG_UNIMPLEMENTED.");
+ }
+}
+
+SshChannelOpenFailure SshIncomingPacket::extractChannelOpenFailure() const
+{
+ Q_ASSERT(isComplete());
+ Q_ASSERT(type() == SSH_MSG_CHANNEL_OPEN_FAILURE);
+
+ SshChannelOpenFailure openFailure;
+ try {
+ quint32 offset = TypeOffset + 1;
+ openFailure.localChannel = SshPacketParser::asUint32(m_data, &offset);
+ openFailure.reasonCode = SshPacketParser::asUint32(m_data, &offset);
+ openFailure.reasonString = QString::fromLocal8Bit(SshPacketParser::asString(m_data, &offset));
+ openFailure.language = SshPacketParser::asString(m_data, &offset);
+ } catch (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);
+ Q_UNUSED(type);
+ 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);
+ Q_UNUSED(type);
+ if (SshPacketParser::asBool(m_data, &offset))
+ throw SshPacketParseException();
+ exitSignal.signal = SshPacketParser::asString(m_data, &offset);
+ exitSignal.coreDumped = SshPacketParser::asBool(m_data, &offset);
+ exitSignal.error = SshPacketParser::asUserString(m_data, &offset);
+ exitSignal.language = SshPacketParser::asString(m_data, &offset);
+ } catch (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 QSsh
diff --git a/src/libs/ssh/sshincomingpacket_p.h b/src/libs/ssh/sshincomingpacket_p.h
new file mode 100644
index 0000000000..84d58783ed
--- /dev/null
+++ b/src/libs/ssh/sshincomingpacket_p.h
@@ -0,0 +1,195 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHINCOMINGPACKET_P_H
+#define SSHINCOMINGPACKET_P_H
+
+#include "sshpacket_p.h"
+
+#include "sshcryptofacility_p.h"
+#include "sshpacketparser_p.h"
+
+#include <QList>
+#include <QString>
+
+namespace QSsh {
+namespace Internal {
+
+class SshKeyExchange;
+
+struct SshKeyExchangeInit
+{
+ char cookie[16];
+ SshNameList keyAlgorithms;
+ SshNameList serverHostKeyAlgorithms;
+ SshNameList encryptionAlgorithmsClientToServer;
+ SshNameList encryptionAlgorithmsServerToClient;
+ SshNameList macAlgorithmsClientToServer;
+ SshNameList macAlgorithmsServerToClient;
+ SshNameList compressionAlgorithmsClientToServer;
+ SshNameList compressionAlgorithmsServerToClient;
+ SshNameList languagesClientToServer;
+ SshNameList languagesServerToClient;
+ bool firstKexPacketFollows;
+};
+
+struct SshKeyExchangeReply
+{
+ QByteArray k_s;
+ QList<Botan::BigInt> 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 SshUnimplemented
+{
+ quint32 invalidMsgSeqNr;
+};
+
+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;
+ SshUnimplemented extractUnimplemented() 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 QSsh
+
+#endif // SSHINCOMINGPACKET_P_H
diff --git a/src/libs/ssh/sshkeycreationdialog.cpp b/src/libs/ssh/sshkeycreationdialog.cpp
new file mode 100644
index 0000000000..ab5595a49a
--- /dev/null
+++ b/src/libs/ssh/sshkeycreationdialog.cpp
@@ -0,0 +1,145 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#include "sshkeycreationdialog.h"
+#include "ui_sshkeycreationdialog.h"
+
+#include "sshkeygenerator.h"
+
+#include <QDir>
+#include <QFile>
+#include <QFileDialog>
+#include <QFileInfo>
+#include <QApplication>
+#include <QDesktopServices>
+#include <QMessageBox>
+
+namespace QSsh {
+
+SshKeyCreationDialog::SshKeyCreationDialog(QWidget *parent)
+ : QDialog(parent), m_keyGenerator(0), m_ui(new Ui::SshKeyCreationDialog)
+{
+ m_ui->setupUi(this);
+ const QString defaultPath = QDesktopServices::storageLocation(QDesktopServices::HomeLocation)
+ + QLatin1String("/.ssh/qtc_id");
+ setPrivateKeyFile(defaultPath);
+
+ connect(m_ui->rsa, SIGNAL(toggled(bool)), this, SLOT(keyTypeChanged()));
+ connect(m_ui->dsa, SIGNAL(toggled(bool)), this, SLOT(keyTypeChanged()));
+ connect(m_ui->privateKeyFileButton, SIGNAL(clicked()), SLOT(handleBrowseButtonClicked()));
+ connect(m_ui->generateButton, SIGNAL(clicked()), this, SLOT(generateKeys()));
+}
+
+SshKeyCreationDialog::~SshKeyCreationDialog()
+{
+ delete m_keyGenerator;
+}
+
+void SshKeyCreationDialog::keyTypeChanged()
+{
+ m_ui->comboBox->setCurrentIndex(0);
+ m_ui->comboBox->setEnabled(m_ui->rsa->isChecked());
+}
+
+void SshKeyCreationDialog::generateKeys()
+{
+ const SshKeyGenerator::KeyType keyType = m_ui->rsa->isChecked()
+ ? SshKeyGenerator::Rsa
+ : SshKeyGenerator::Dsa;
+
+ if (!m_keyGenerator)
+ m_keyGenerator = new SshKeyGenerator;
+
+ QApplication::setOverrideCursor(Qt::BusyCursor);
+ const bool success = m_keyGenerator->generateKeys(keyType, SshKeyGenerator::Mixed,
+ m_ui->comboBox->currentText().toUShort());
+ QApplication::restoreOverrideCursor();
+
+ if (success) {
+ saveKeys();
+ } else {
+ QMessageBox::critical(this, tr("Key Generation Failed"), m_keyGenerator->error());
+ }
+}
+
+void SshKeyCreationDialog::handleBrowseButtonClicked()
+{
+ const QString filePath = QFileDialog::getSaveFileName(this, tr("Choose Private Key File Name"));
+ if (!filePath.isEmpty())
+ setPrivateKeyFile(filePath);
+}
+
+void SshKeyCreationDialog::setPrivateKeyFile(const QString &filePath)
+{
+ m_ui->privateKeyFileValueLabel->setText(filePath);
+ m_ui->generateButton->setEnabled(!privateKeyFilePath().isEmpty());
+ m_ui->publicKeyFileLabel->setText(filePath + QLatin1String(".pub"));
+}
+
+void SshKeyCreationDialog::saveKeys()
+{
+ const QString parentDir = QFileInfo(privateKeyFilePath()).dir().path();
+ if (!QDir::root().mkpath(parentDir)) {
+ QMessageBox::critical(this, tr("Failure To Save Key File"),
+ tr("Failed to create directory: '%1'.").arg(parentDir));
+ return;
+ }
+
+ QFile privateKeyFile(privateKeyFilePath());
+ if (!privateKeyFile.open(QIODevice::WriteOnly)
+ || !privateKeyFile.write(m_keyGenerator->privateKey())) {
+ QMessageBox::critical(this, tr("Saving Private Key File failed"),
+ tr("The private key file could not be saved: %1").arg(privateKeyFile.errorString()));
+ return;
+ }
+ QFile::setPermissions(privateKeyFilePath(), QFile::ReadOwner | QFile::WriteOwner);
+
+ QFile publicKeyFile(publicKeyFilePath());
+ if (!publicKeyFile.open(QIODevice::WriteOnly)
+ || !publicKeyFile.write(m_keyGenerator->publicKey())) {
+ QMessageBox::critical(this, tr("Saving Public Key File failed"),
+ tr("The public key file could not be saved: %1").arg(publicKeyFile.errorString()));
+ return;
+ }
+
+ accept();
+}
+
+QString SshKeyCreationDialog::privateKeyFilePath() const
+{
+ return m_ui->privateKeyFileValueLabel->text();
+}
+
+QString SshKeyCreationDialog::publicKeyFilePath() const
+{
+ return m_ui->publicKeyFileLabel->text();
+}
+
+} // namespace QSsh
diff --git a/src/libs/ssh/sshkeycreationdialog.h b/src/libs/ssh/sshkeycreationdialog.h
new file mode 100644
index 0000000000..7f30792a3f
--- /dev/null
+++ b/src/libs/ssh/sshkeycreationdialog.h
@@ -0,0 +1,70 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHKEYCREATIONDIALOG_H
+#define SSHKEYCREATIONDIALOG_H
+
+#include "ssh_global.h"
+
+#include <QDialog>
+
+namespace QSsh {
+class SshKeyGenerator;
+
+namespace Ui { class SshKeyCreationDialog; }
+
+class QSSH_EXPORT SshKeyCreationDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ SshKeyCreationDialog(QWidget *parent = 0);
+ ~SshKeyCreationDialog();
+
+ QString privateKeyFilePath() const;
+ QString publicKeyFilePath() const;
+
+private slots:
+ void keyTypeChanged();
+ void generateKeys();
+ void handleBrowseButtonClicked();
+
+private:
+ void setPrivateKeyFile(const QString &filePath);
+ void saveKeys();
+
+private:
+ SshKeyGenerator *m_keyGenerator;
+ Ui::SshKeyCreationDialog *m_ui;
+};
+
+} // namespace QSsh
+
+#endif // SSHKEYCREATIONDIALOG_H
diff --git a/src/libs/ssh/sshkeycreationdialog.ui b/src/libs/ssh/sshkeycreationdialog.ui
new file mode 100644
index 0000000000..d49681e65b
--- /dev/null
+++ b/src/libs/ssh/sshkeycreationdialog.ui
@@ -0,0 +1,272 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QSsh::SshKeyCreationDialog</class>
+ <widget class="QDialog" name="QSsh::SshKeyCreationDialog">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>295</width>
+ <height>223</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>SSH Key Configuration</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Options</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="keyAlgo">
+ <property name="text">
+ <string>Key algorithm:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QRadioButton" name="rsa">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;RSA</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="dsa">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;DSA</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="keySize">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Key &amp;size:</string>
+ </property>
+ <property name="buddy">
+ <cstring>comboBox</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QComboBox" name="comboBox">
+ <item>
+ <property name="text">
+ <string notr="true">1024</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">2048</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">4096</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="privateKeyFileLabel">
+ <property name="text">
+ <string>Private key file:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="privateKeyFileValueLabel">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="privateKeyFileButton">
+ <property name="text">
+ <string>Browse...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Public key file:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="publicKeyFileLabel">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="generateButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Generate And Save Key Pair</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="closeButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Cancel</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>closeButton</sender>
+ <signal>clicked()</signal>
+ <receiver>QSsh::SshKeyCreationDialog</receiver>
+ <slot>close()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>195</x>
+ <y>184</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>381</x>
+ <y>107</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/libs/ssh/sshkeyexchange.cpp b/src/libs/ssh/sshkeyexchange.cpp
new file mode 100644
index 0000000000..333f394dea
--- /dev/null
+++ b/src/libs/ssh/sshkeyexchange.cpp
@@ -0,0 +1,225 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "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>
+
+#ifdef CREATOR_SSH_DEBUG
+#include <iostream>
+#endif
+#include <string>
+
+using namespace Botan;
+
+namespace QSsh {
+namespace Internal {
+
+namespace {
+
+ // For debugging
+ void printNameList(const char *listName, const SshNameList &list)
+ {
+#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
+ }
+
+#ifdef CREATOR_SSH_DEBUG
+ void printData(const char *name, const QByteArray &data)
+ {
+ std::cerr << std::hex;
+ qDebug("The client thinks the %s has length %d and is:", name, data.count());
+ for (int i = 0; i < data.count(); ++i)
+ std::cerr << (static_cast<unsigned int>(data.at(i)) & 0xff) << ' ';
+ std::cerr << std::endl;
+ }
+#endif
+
+} // anonymous namespace
+
+SshKeyExchange::SshKeyExchange(SshSendFacility &sendFacility)
+ : m_sendFacility(sendFacility)
+{
+}
+
+SshKeyExchange::~SshKeyExchange() {}
+
+void SshKeyExchange::sendKexInitPacket(const QByteArray &serverId)
+{
+ m_serverId = serverId;
+ m_clientKexInitPayload = m_sendFacility.sendKeyExchangeInitPacket();
+}
+
+bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit)
+{
+#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))));
+
+ m_serverKexInitPayload = serverKexInit.payLoad();
+ 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);
+
+#ifdef CREATOR_SSH_DEBUG
+ printData("Client Id", AbstractSshPacket::encodeString(clientId));
+ printData("Server Id", AbstractSshPacket::encodeString(m_serverId));
+ printData("Client Payload", AbstractSshPacket::encodeString(m_clientKexInitPayload));
+ printData("Server payload", AbstractSshPacket::encodeString(m_serverKexInitPayload));
+ printData("K_S", reply.k_s);
+ printData("y", AbstractSshPacket::encodeMpInt(m_dhKey->get_y()));
+ printData("f", AbstractSshPacket::encodeMpInt(reply.f));
+ printData("K", m_k);
+ printData("Concatenated data", concatenatedData);
+ printData("H", m_h);
+#endif // CREATOR_SSH_DEBUG
+
+ 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 QSsh
diff --git a/src/libs/ssh/sshkeyexchange_p.h b/src/libs/ssh/sshkeyexchange_p.h
new file mode 100644
index 0000000000..eb6a13f30a
--- /dev/null
+++ b/src/libs/ssh/sshkeyexchange_p.h
@@ -0,0 +1,90 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHKEYEXCHANGE_P_H
+#define SSHKEYEXCHANGE_P_H
+
+#include <botan/dh.h>
+
+#include <QByteArray>
+#include <QScopedPointer>
+
+namespace Botan { class HashFunction; }
+
+namespace QSsh {
+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 QSsh
+
+#endif // SSHKEYEXCHANGE_P_H
diff --git a/src/libs/ssh/sshkeygenerator.cpp b/src/libs/ssh/sshkeygenerator.cpp
new file mode 100644
index 0000000000..f71a2232c7
--- /dev/null
+++ b/src/libs/ssh/sshkeygenerator.cpp
@@ -0,0 +1,201 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshkeygenerator.h"
+
+#include "sshbotanconversions_p.h"
+#include "sshcapabilities_p.h"
+#include "sshpacket_p.h"
+
+#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 <QDateTime>
+#include <QInputDialog>
+
+#include <string>
+
+namespace QSsh {
+
+using namespace Botan;
+using namespace Internal;
+
+SshKeyGenerator::SshKeyGenerator() : m_type(Rsa)
+{
+}
+
+bool SshKeyGenerator::generateKeys(KeyType type, PrivateKeyFormat format, int keySize,
+ EncryptionMode encryptionMode)
+{
+ m_type = type;
+ m_encryptionMode = encryptionMode;
+
+ try {
+ AutoSeeded_RNG rng;
+ KeyPtr key;
+ if (m_type == Rsa)
+ key = KeyPtr(new RSA_PrivateKey(rng, keySize));
+ else
+ key = KeyPtr(new DSA_PrivateKey(rng, DL_Group(rng, DL_Group::DSA_Kosherizer, keySize)));
+ switch (format) {
+ case Pkcs8:
+ generatePkcs8KeyStrings(key, rng);
+ break;
+ case OpenSsl:
+ generateOpenSslKeyStrings(key);
+ break;
+ case Mixed:
+ default:
+ generatePkcs8KeyString(key, true, rng);
+ generateOpenSslPublicKeyString(key);
+ }
+ return true;
+ } catch (Botan::Exception &e) {
+ m_error = tr("Error generating key: %1").arg(QString::fromAscii(e.what()));
+ return false;
+ }
+}
+
+void SshKeyGenerator::generatePkcs8KeyStrings(const KeyPtr &key, Botan::RandomNumberGenerator &rng)
+{
+ generatePkcs8KeyString(key, false, rng);
+ generatePkcs8KeyString(key, true, rng);
+}
+
+void SshKeyGenerator::generatePkcs8KeyString(const KeyPtr &key, bool privateKey,
+ Botan::RandomNumberGenerator &rng)
+{
+ Pipe pipe;
+ pipe.start_msg();
+ QByteArray *keyData;
+ if (privateKey) {
+ QString password;
+ if (m_encryptionMode == DoOfferEncryption)
+ password = getPassword();
+ if (!password.isEmpty())
+ PKCS8::encrypt_key(*key, pipe, rng, password.toLocal8Bit().data());
+ else
+ 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);
+}
+
+void SshKeyGenerator::generateOpenSslKeyStrings(const KeyPtr &key)
+{
+ generateOpenSslPublicKeyString(key);
+ generateOpenSslPrivateKeyString(key);
+}
+
+void SshKeyGenerator::generateOpenSslPublicKeyString(const KeyPtr &key)
+{
+ QList<BigInt> params;
+ QByteArray keyId;
+ if (m_type == Rsa) {
+ const QSharedPointer<RSA_PrivateKey> rsaKey = key.dynamicCast<RSA_PrivateKey>();
+ params << rsaKey->get_e() << rsaKey->get_n();
+ keyId = SshCapabilities::PubKeyRsa;
+ } else {
+ const QSharedPointer<DSA_PrivateKey> dsaKey = key.dynamicCast<DSA_PrivateKey>();
+ params << dsaKey->group_p() << dsaKey->group_q() << dsaKey->group_g() << dsaKey->get_y();
+ keyId = SshCapabilities::PubKeyDss;
+ }
+
+ QByteArray publicKeyBlob = AbstractSshPacket::encodeString(keyId);
+ foreach (const BigInt &b, params)
+ publicKeyBlob += AbstractSshPacket::encodeMpInt(b);
+ publicKeyBlob = publicKeyBlob.toBase64();
+ const QByteArray id = "QtCreator/"
+ + QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8();
+ m_publicKey = keyId + ' ' + publicKeyBlob + ' ' + id;
+}
+
+void SshKeyGenerator::generateOpenSslPrivateKeyString(const KeyPtr &key)
+{
+ QList<BigInt> params;
+ QByteArray keyId;
+ const char *label;
+ if (m_type == Rsa) {
+ const QSharedPointer<RSA_PrivateKey> rsaKey
+ = key.dynamicCast<RSA_PrivateKey>();
+ params << rsaKey->get_n() << rsaKey->get_e() << rsaKey->get_d() << rsaKey->get_p()
+ << rsaKey->get_q();
+ keyId = SshCapabilities::PubKeyRsa;
+ label = "RSA PRIVATE KEY";
+ } else {
+ const QSharedPointer<DSA_PrivateKey> dsaKey = key.dynamicCast<DSA_PrivateKey>();
+ params << dsaKey->group_p() << dsaKey->group_q() << dsaKey->group_g() << dsaKey->get_y()
+ << dsaKey->get_x();
+ keyId = SshCapabilities::PubKeyDss;
+ label = "DSA PRIVATE KEY";
+ }
+
+ DER_Encoder encoder;
+ encoder.start_cons(SEQUENCE).encode(0U);
+ foreach (const BigInt &b, params)
+ encoder.encode(b);
+ encoder.end_cons();
+ m_privateKey = QByteArray(PEM_Code::encode (encoder.get_contents(), label).c_str());
+}
+
+QString SshKeyGenerator::getPassword() const
+{
+ QInputDialog d;
+ d.setInputMode(QInputDialog::TextInput);
+ d.setTextEchoMode(QLineEdit::Password);
+ d.setWindowTitle(tr("Password for Private Key"));
+ d.setLabelText(tr("It is recommended that you secure your private key\n"
+ "with a password, which you can enter below."));
+ d.setOkButtonText(tr("Encrypt Key File"));
+ d.setCancelButtonText(tr("Do Not Encrypt Key File"));
+ int result = QDialog::Accepted;
+ QString password;
+ while (result == QDialog::Accepted && password.isEmpty()) {
+ result = d.exec();
+ password = d.textValue();
+ }
+ return result == QDialog::Accepted ? password : QString();
+}
+
+} // namespace QSsh
diff --git a/src/libs/ssh/sshkeygenerator.h b/src/libs/ssh/sshkeygenerator.h
new file mode 100644
index 0000000000..09bf1804ed
--- /dev/null
+++ b/src/libs/ssh/sshkeygenerator.h
@@ -0,0 +1,85 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHKEYGENERATOR_H
+#define SSHKEYGENERATOR_H
+
+#include "ssh_global.h"
+
+#include <QCoreApplication>
+#include <QSharedPointer>
+
+namespace Botan {
+ class Private_Key;
+ class RandomNumberGenerator;
+}
+
+namespace QSsh {
+
+class QSSH_EXPORT SshKeyGenerator
+{
+ Q_DECLARE_TR_FUNCTIONS(SshKeyGenerator)
+public:
+ enum KeyType { Rsa, Dsa };
+ enum PrivateKeyFormat { Pkcs8, OpenSsl, Mixed };
+ enum EncryptionMode { DoOfferEncryption, DoNotOfferEncryption }; // Only relevant for Pkcs8 format.
+
+ SshKeyGenerator();
+ bool generateKeys(KeyType type, PrivateKeyFormat format, int keySize,
+ EncryptionMode encryptionMode = DoOfferEncryption);
+
+ QString error() const { return m_error; }
+ QByteArray privateKey() const { return m_privateKey; }
+ QByteArray publicKey() const { return m_publicKey; }
+ KeyType type() const { return m_type; }
+
+private:
+ typedef QSharedPointer<Botan::Private_Key> KeyPtr;
+
+ void generatePkcs8KeyStrings(const KeyPtr &key, Botan::RandomNumberGenerator &rng);
+ void generatePkcs8KeyString(const KeyPtr &key, bool privateKey,
+ Botan::RandomNumberGenerator &rng);
+ void generateOpenSslKeyStrings(const KeyPtr &key);
+ void generateOpenSslPrivateKeyString(const KeyPtr &key);
+ void generateOpenSslPublicKeyString(const KeyPtr &key);
+ QString getPassword() const;
+
+ QString m_error;
+ QByteArray m_publicKey;
+ QByteArray m_privateKey;
+ KeyType m_type;
+ EncryptionMode m_encryptionMode;
+};
+
+} // namespace QSsh
+
+#endif // SSHKEYGENERATOR_H
diff --git a/src/libs/ssh/sshkeypasswordretriever.cpp b/src/libs/ssh/sshkeypasswordretriever.cpp
new file mode 100644
index 0000000000..5e725c7f55
--- /dev/null
+++ b/src/libs/ssh/sshkeypasswordretriever.cpp
@@ -0,0 +1,65 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#include "sshkeypasswordretriever_p.h"
+
+#include <QString>
+#include <QApplication>
+#include <QInputDialog>
+
+#include <iostream>
+
+namespace QSsh {
+namespace Internal {
+
+std::string SshKeyPasswordRetriever::get_passphrase(const std::string &, const std::string &,
+ UI_Result &result) const
+{
+ const bool hasGui = dynamic_cast<QApplication *>(QApplication::instance());
+ if (hasGui) {
+ bool ok;
+ const QString &password = QInputDialog::getText(0,
+ QCoreApplication::translate("QSsh::Ssh", "Password Required"),
+ QCoreApplication::translate("QSsh::Ssh", "Please enter the password for your private key."),
+ QLineEdit::Password, QString(), &ok);
+ result = ok ? OK : CANCEL_ACTION;
+ return std::string(password.toLocal8Bit().data());
+ } else {
+ result = OK;
+ std::string password;
+ std::cout << "Please enter the password for your private key (set echo off beforehand!): " << std::flush;
+ std::cin >> password;
+ return password;
+ }
+}
+
+} // namespace Internal
+} // namespace QSsh
diff --git a/src/libs/ssh/sshkeypasswordretriever_p.h b/src/libs/ssh/sshkeypasswordretriever_p.h
new file mode 100644
index 0000000000..b3a0b0f09e
--- /dev/null
+++ b/src/libs/ssh/sshkeypasswordretriever_p.h
@@ -0,0 +1,52 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#ifndef KEYPASSWORDRETRIEVER_H
+#define KEYPASSWORDRETRIEVER_H
+
+#include <botan/ui.h>
+
+#include <string>
+
+namespace QSsh {
+namespace Internal {
+
+class SshKeyPasswordRetriever : public Botan::User_Interface
+{
+public:
+ std::string get_passphrase(const std::string &what, const std::string &source,
+ UI_Result &result) const;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // KEYPASSWORDRETRIEVER_H
diff --git a/src/libs/ssh/sshoutgoingpacket.cpp b/src/libs/ssh/sshoutgoingpacket.cpp
new file mode 100644
index 0000000000..04a38d9927
--- /dev/null
+++ b/src/libs/ssh/sshoutgoingpacket.cpp
@@ -0,0 +1,325 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshoutgoingpacket_p.h"
+
+#include "sshcapabilities_p.h"
+#include "sshcryptofacility_p.h"
+
+#include <QtEndian>
+
+namespace QSsh {
+namespace Internal {
+
+SshOutgoingPacket::SshOutgoingPacket(const SshEncryptionFacility &encrypter,
+ const quint32 &seqNr) : m_encrypter(encrypter), m_seqNr(seqNr)
+{
+}
+
+quint32 SshOutgoingPacket::cipherBlockSize() const
+{
+ return qMax(m_encrypter.cipherBlockSize(), 4U);
+}
+
+quint32 SshOutgoingPacket::macLength() const
+{
+ return m_encrypter.macLength();
+}
+
+QByteArray SshOutgoingPacket::generateKeyExchangeInitPacket()
+{
+ const QByteArray &supportedkeyExchangeMethods
+ = encodeNameList(SshCapabilities::KeyExchangeMethods);
+ const QByteArray &supportedPublicKeyAlgorithms
+ = encodeNameList(SshCapabilities::PublicKeyAlgorithms);
+ const QByteArray &supportedEncryptionAlgorithms
+ = encodeNameList(SshCapabilities::EncryptionAlgorithms);
+ const QByteArray &supportedMacAlgorithms
+ = encodeNameList(SshCapabilities::MacAlgorithms);
+ const QByteArray &supportedCompressionAlgorithms
+ = encodeNameList(SshCapabilities::CompressionAlgorithms);
+ const QByteArray &supportedLanguages = encodeNameList(QList<QByteArray>());
+
+ init(SSH_MSG_KEXINIT);
+ m_data += m_encrypter.getRandomNumbers(16);
+ m_data.append(supportedkeyExchangeMethods);
+ m_data.append(supportedPublicKeyAlgorithms);
+ m_data.append(supportedEncryptionAlgorithms)
+ .append(supportedEncryptionAlgorithms);
+ m_data.append(supportedMacAlgorithms).append(supportedMacAlgorithms);
+ m_data.append(supportedCompressionAlgorithms)
+ .append(supportedCompressionAlgorithms);
+ m_data.append(supportedLanguages).append(supportedLanguages);
+ appendBool(false); // No guessed packet.
+ m_data.append(QByteArray(4, 0)); // Reserved.
+ QByteArray payload = m_data.mid(PayloadOffset);
+ finalize();
+ return payload;
+}
+
+void SshOutgoingPacket::generateKeyDhInitPacket(const Botan::BigInt &e)
+{
+ init(SSH_MSG_KEXDH_INIT).appendMpInt(e).finalize();
+}
+
+void SshOutgoingPacket::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::generateIgnorePacket()
+{
+ init(SSH_MSG_IGNORE).finalize();
+}
+
+void SshOutgoingPacket::generateInvalidMessagePacket()
+{
+ init(SSH_MSG_INVALID).finalize();
+}
+
+void SshOutgoingPacket::generateSessionPacket(quint32 channelId,
+ quint32 windowSize, quint32 maxPacketSize)
+{
+ init(SSH_MSG_CHANNEL_OPEN).appendString("session").appendInt(channelId)
+ .appendInt(windowSize).appendInt(maxPacketSize).finalize();
+}
+
+void SshOutgoingPacket::generateEnvPacket(quint32 remoteChannel,
+ const QByteArray &var, const QByteArray &value)
+{
+ init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("env")
+ .appendBool(false).appendString(var).appendString(value).finalize();
+}
+
+void SshOutgoingPacket::generatePtyRequestPacket(quint32 remoteChannel,
+ const SshPseudoTerminal &terminal)
+{
+ init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel)
+ .appendString("pty-req").appendBool(false)
+ .appendString(terminal.termType).appendInt(terminal.columnCount)
+ .appendInt(terminal.rowCount).appendInt(0).appendInt(0);
+ QByteArray modeString;
+ for (SshPseudoTerminal::ModeMap::ConstIterator it = terminal.modes.constBegin();
+ it != terminal.modes.constEnd(); ++it) {
+ modeString += char(it.key());
+ modeString += encodeInt(it.value());
+ }
+ modeString += char(0); // TTY_OP_END
+ appendString(modeString).finalize();
+}
+
+void SshOutgoingPacket::generateExecPacket(quint32 remoteChannel,
+ const QByteArray &command)
+{
+ init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("exec")
+ .appendBool(true).appendString(command).finalize();
+}
+
+void SshOutgoingPacket::generateShellPacket(quint32 remoteChannel)
+{
+ init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("shell")
+ .appendBool(true).finalize();
+}
+
+void SshOutgoingPacket::generateSftpPacket(quint32 remoteChannel)
+{
+ init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel)
+ .appendString("subsystem").appendBool(true).appendString("sftp")
+ .finalize();
+}
+
+void SshOutgoingPacket::generateWindowAdjustPacket(quint32 remoteChannel,
+ quint32 bytesToAdd)
+{
+ init(SSH_MSG_CHANNEL_WINDOW_ADJUST).appendInt(remoteChannel)
+ .appendInt(bytesToAdd).finalize();
+}
+
+void SshOutgoingPacket::generateChannelDataPacket(quint32 remoteChannel,
+ const QByteArray &data)
+{
+ init(SSH_MSG_CHANNEL_DATA).appendInt(remoteChannel).appendString(data)
+ .finalize();
+}
+
+void SshOutgoingPacket::generateChannelSignalPacket(quint32 remoteChannel,
+ const QByteArray &signalName)
+{
+ init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel)
+ .appendString("signal").appendBool(false).appendString(signalName)
+ .finalize();
+}
+
+void SshOutgoingPacket::generateChannelEofPacket(quint32 remoteChannel)
+{
+ init(SSH_MSG_CHANNEL_EOF).appendInt(remoteChannel).finalize();
+}
+
+void SshOutgoingPacket::generateChannelClosePacket(quint32 remoteChannel)
+{
+ init(SSH_MSG_CHANNEL_CLOSE).appendInt(remoteChannel).finalize();
+}
+
+void SshOutgoingPacket::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();
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Sending packet of size %d", rawData().count());
+#endif
+ Q_ASSERT(isComplete());
+}
+
+int SshOutgoingPacket::sizeDivisor() const
+{
+ return qMax(cipherBlockSize(), 8U);
+}
+
+QByteArray SshOutgoingPacket::encodeNameList(const QList<QByteArray> &list)
+{
+ QByteArray data;
+ data.resize(4);
+ for (int i = 0; i < list.count(); ++i) {
+ if (i > 0)
+ data.append(',');
+ data.append(list.at(i));
+ }
+ AbstractSshPacket::setLengthField(data);
+ return data;
+}
+
+} // namespace Internal
+} // namespace QSsh
diff --git a/src/libs/ssh/sshoutgoingpacket_p.h b/src/libs/ssh/sshoutgoingpacket_p.h
new file mode 100644
index 0000000000..4a12218ebc
--- /dev/null
+++ b/src/libs/ssh/sshoutgoingpacket_p.h
@@ -0,0 +1,108 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHOUTGOINGPACKET_P_H
+#define SSHOUTGOINGPACKET_P_H
+
+#include "sshpacket_p.h"
+
+#include "sshpseudoterminal.h"
+
+namespace QSsh {
+namespace Internal {
+
+class SshEncryptionFacility;
+
+class SshOutgoingPacket : public AbstractSshPacket
+{
+public:
+ SshOutgoingPacket(const SshEncryptionFacility &encrypter,
+ const quint32 &seqNr);
+
+ QByteArray generateKeyExchangeInitPacket(); // Returns payload.
+ void generateKeyDhInitPacket(const Botan::BigInt &e);
+ void 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 generateIgnorePacket();
+ void generateInvalidMessagePacket();
+ void generateSessionPacket(quint32 channelId, quint32 windowSize,
+ quint32 maxPacketSize);
+ void generateEnvPacket(quint32 remoteChannel, const QByteArray &var,
+ const QByteArray &value);
+ void generatePtyRequestPacket(quint32 remoteChannel,
+ const SshPseudoTerminal &terminal);
+ void generateExecPacket(quint32 remoteChannel, const QByteArray &command);
+ void generateShellPacket(quint32 remoteChannel);
+ void generateSftpPacket(quint32 remoteChannel);
+ void generateWindowAdjustPacket(quint32 remoteChannel, quint32 bytesToAdd);
+ void generateChannelDataPacket(quint32 remoteChannel,
+ const QByteArray &data);
+ void generateChannelSignalPacket(quint32 remoteChannel,
+ const QByteArray &signalName);
+ void generateChannelEofPacket(quint32 remoteChannel);
+ void generateChannelClosePacket(quint32 remoteChannel);
+
+private:
+ virtual quint32 cipherBlockSize() const;
+ virtual quint32 macLength() const;
+
+ static QByteArray encodeNameList(const QList<QByteArray> &list);
+
+ void generateServiceRequest(const QByteArray &service);
+
+ SshOutgoingPacket &init(SshPacketType type);
+ SshOutgoingPacket &setPadding();
+ SshOutgoingPacket &encrypt();
+ void finalize();
+
+ SshOutgoingPacket &appendInt(quint32 val);
+ SshOutgoingPacket &appendString(const QByteArray &string);
+ SshOutgoingPacket &appendMpInt(const Botan::BigInt &number);
+ SshOutgoingPacket &appendBool(bool b);
+ int sizeDivisor() const;
+
+ const SshEncryptionFacility &m_encrypter;
+ const quint32 &m_seqNr;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHOUTGOINGPACKET_P_H
diff --git a/src/libs/ssh/sshpacket.cpp b/src/libs/ssh/sshpacket.cpp
new file mode 100644
index 0000000000..3e6e408587
--- /dev/null
+++ b/src/libs/ssh/sshpacket.cpp
@@ -0,0 +1,168 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshpacket_p.h"
+
+#include "sshcapabilities_p.h"
+#include "sshcryptofacility_p.h"
+#include "sshexception_p.h"
+#include "sshpacketparser_p.h"
+
+#include <QDebug>
+
+#include <cctype>
+
+namespace QSsh {
+namespace Internal {
+
+const quint32 AbstractSshPacket::PaddingLengthOffset = 4;
+const quint32 AbstractSshPacket::PayloadOffset = PaddingLengthOffset + 1;
+const quint32 AbstractSshPacket::TypeOffset = PayloadOffset;
+const quint32 AbstractSshPacket::MinPaddingLength = 4;
+
+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));
+}
+
+QByteArray AbstractSshPacket::payLoad() const
+{
+ return QByteArray(m_data.constData() + PayloadOffset,
+ length() - paddingLength() - 1);
+}
+
+void AbstractSshPacket::printRawBytes() const
+{
+ printByteArray(m_data);
+}
+
+QByteArray AbstractSshPacket::encodeString(const QByteArray &string)
+{
+ QByteArray data;
+ data.resize(4);
+ data += string;
+ setLengthField(data);
+ return data;
+}
+
+QByteArray AbstractSshPacket::encodeMpInt(const Botan::BigInt &number)
+{
+ if (number.is_zero())
+ return QByteArray(4, 0);
+
+ int stringLength = 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 QSsh
diff --git a/src/libs/ssh/sshpacket_p.h b/src/libs/ssh/sshpacket_p.h
new file mode 100644
index 0000000000..22a7a9e826
--- /dev/null
+++ b/src/libs/ssh/sshpacket_p.h
@@ -0,0 +1,146 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHPACKET_P_H
+#define SSHPACKET_P_H
+
+#include "sshexception_p.h"
+
+#include <QtEndian>
+#include <QByteArray>
+#include <QList>
+
+#include <botan/bigint.h>
+
+namespace QSsh {
+namespace Internal {
+
+enum SshPacketType {
+ SSH_MSG_DISCONNECT = 1,
+ SSH_MSG_IGNORE = 2,
+ SSH_MSG_UNIMPLEMENTED = 3,
+ SSH_MSG_DEBUG = 4,
+ SSH_MSG_SERVICE_REQUEST = 5,
+ SSH_MSG_SERVICE_ACCEPT = 6,
+
+ SSH_MSG_KEXINIT = 20,
+ SSH_MSG_NEWKEYS = 21,
+ SSH_MSG_KEXDH_INIT = 30,
+ SSH_MSG_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,
+
+ // TODO: We currently take no precautions against sending these messages
+ // during a key re-exchange, which is not allowed.
+ SSH_MSG_CHANNEL_OPEN = 90,
+ SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91,
+ SSH_MSG_CHANNEL_OPEN_FAILURE = 92,
+ SSH_MSG_CHANNEL_WINDOW_ADJUST = 93,
+ SSH_MSG_CHANNEL_DATA = 94,
+ SSH_MSG_CHANNEL_EXTENDED_DATA = 95,
+ SSH_MSG_CHANNEL_EOF = 96,
+ SSH_MSG_CHANNEL_CLOSE = 97,
+ SSH_MSG_CHANNEL_REQUEST = 98,
+ SSH_MSG_CHANNEL_SUCCESS = 99,
+ SSH_MSG_CHANNEL_FAILURE = 100,
+
+ // Not completely safe, since the server may actually understand this
+ // message type as an extension. Switch to a different value in that case
+ // (between 128 and 191).
+ SSH_MSG_INVALID = 128
+};
+
+enum 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; }
+
+ QByteArray payLoad() const;
+
+protected:
+ AbstractSshPacket();
+
+ virtual quint32 cipherBlockSize() const = 0;
+ virtual quint32 macLength() const = 0;
+ virtual void calculateLength() const;
+
+ quint32 length() const;
+ int paddingLength() const;
+ quint32 minPacketSize() const;
+ quint32 currentDataSize() const { return m_data.size(); }
+ QByteArray generateMac(const SshAbstractCryptoFacility &crypt,
+ quint32 seqNr) const;
+
+ static const quint32 PaddingLengthOffset;
+ static const quint32 PayloadOffset;
+ static const quint32 TypeOffset;
+ static const quint32 MinPaddingLength;
+
+ mutable QByteArray m_data;
+ mutable quint32 m_length;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHPACKET_P_H
diff --git a/src/libs/ssh/sshpacketparser.cpp b/src/libs/ssh/sshpacketparser.cpp
new file mode 100644
index 0000000000..c5197fc01c
--- /dev/null
+++ b/src/libs/ssh/sshpacketparser.cpp
@@ -0,0 +1,156 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshpacketparser_p.h"
+
+#include <cctype>
+
+namespace QSsh {
+namespace Internal {
+
+namespace { quint32 size(const QByteArray &data) { return data.size(); } }
+
+QString SshPacketParser::asUserString(const QByteArray &rawString)
+{
+ QByteArray filteredString;
+ filteredString.resize(rawString.size());
+ for (int i = 0; i < rawString.size(); ++i) {
+ const char c = rawString.at(i);
+ filteredString[i]
+ = std::isprint(c) || c == '\n' || c == '\r' || c == '\t' ? c : '?';
+ }
+ return QString::fromUtf8(filteredString);
+}
+
+bool SshPacketParser::asBool(const QByteArray &data, quint32 offset)
+{
+ if (size(data) <= offset)
+ throw SshPacketParseException();
+ return data.at(offset);
+}
+
+bool SshPacketParser::asBool(const QByteArray &data, quint32 *offset)
+{
+ bool b = asBool(data, *offset);
+ ++(*offset);
+ return b;
+}
+
+
+quint32 SshPacketParser::asUint32(const QByteArray &data, quint32 offset)
+{
+ if (size(data) < offset + 4)
+ throw SshPacketParseException();
+ 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 QSsh
diff --git a/src/libs/ssh/sshpacketparser_p.h b/src/libs/ssh/sshpacketparser_p.h
new file mode 100644
index 0000000000..775dc5e975
--- /dev/null
+++ b/src/libs/ssh/sshpacketparser_p.h
@@ -0,0 +1,84 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHPACKETPARSER_P_H
+#define SSHPACKETPARSER_P_H
+
+#include <botan/bigint.h>
+
+#include <QByteArray>
+#include <QList>
+#include <QString>
+
+namespace QSsh {
+namespace Internal {
+
+struct SshNameList
+{
+ SshNameList() : originalLength(0) {}
+ SshNameList(quint32 originalLength) : originalLength(originalLength) {}
+ quint32 originalLength;
+ QList<QByteArray> names;
+};
+
+class SshPacketParseException { };
+
+// This class's functions try to read a byte array at a certain offset
+// as the respective chunk of data as specified in the SSH RFCs.
+// If they succeed, they update the offset, so they can easily
+// be called in succession by client code.
+// For convenience, some have also versions that don't update the offset,
+// so they can be called with rvalues if the new value is not needed.
+// If they fail, they throw an SshPacketParseException.
+class SshPacketParser
+{
+public:
+ static bool asBool(const QByteArray &data, quint32 offset);
+ static bool asBool(const QByteArray &data, quint32 *offset);
+ static quint16 asUint16(const QByteArray &data, quint32 offset);
+ static quint16 asUint16(const QByteArray &data, quint32 *offset);
+ static quint64 asUint64(const QByteArray &data, quint32 offset);
+ static quint64 asUint64(const QByteArray &data, quint32 *offset);
+ static quint32 asUint32(const QByteArray &data, quint32 offset);
+ static quint32 asUint32(const QByteArray &data, quint32 *offset);
+ static QByteArray asString(const QByteArray &data, quint32 *offset);
+ static QString asUserString(const QByteArray &data, quint32 *offset);
+ static SshNameList asNameList(const QByteArray &data, quint32 *offset);
+ static Botan::BigInt asBigInt(const QByteArray &data, quint32 *offset);
+
+ static QString asUserString(const QByteArray &rawString);
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHPACKETPARSER_P_H
diff --git a/src/libs/ssh/sshpseudoterminal.h b/src/libs/ssh/sshpseudoterminal.h
new file mode 100644
index 0000000000..23875ef939
--- /dev/null
+++ b/src/libs/ssh/sshpseudoterminal.h
@@ -0,0 +1,120 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHPSEUDOTERMINAL_H
+#define SSHPSEUDOTERMINAL_H
+
+#include "ssh_global.h"
+
+#include <QByteArray>
+#include <QHash>
+
+namespace QSsh {
+
+class QSSH_EXPORT SshPseudoTerminal
+{
+ public:
+ explicit SshPseudoTerminal(const QByteArray &termType = "vt100",
+ int rowCount = 24, int columnCount = 80)
+ : termType(termType), rowCount(rowCount), columnCount(columnCount) {}
+
+ QByteArray termType;
+ int rowCount;
+ int columnCount;
+
+ enum Mode {
+ VINTR = 1, // Interrupt character.
+ VQUIT = 2, // The quit character (sends SIGQUIT signal on POSIX systems).
+ VERASE = 3, // Erase the character to left of the cursor.
+ VKILL = 4, // Kill the current input line.
+ VEOF = 5, // End-of-file character (sends EOF from the terminal).
+ VEOL = 6, // End-of-line character in addition to carriage return and/or linefeed.
+ VEOL2 = 7, // Additional end-of-line character.
+ VSTART = 8, // Continues paused output (normally control-Q).
+ VSTOP = 9, // Pauses output (normally control-S).
+ VSUSP = 10, // Suspends the current program.
+ VDSUSP = 11, // Another suspend character.
+ VREPRINT = 12, // Reprints the current input line.
+ VWERASE = 13, // Erases a word left of cursor.
+ VLNEXT = 14, // Enter the next character typed literally, even if it is a special character.
+ VFLUSH = 15, // Character to flush output.
+ VSWTCH = 16, // Switch to a different shell layer.
+ VSTATUS = 17, // Prints system status line (load, command, pid, etc).
+ VDISCARD = 18, // Toggles the flushing of terminal output.
+
+ IGNPAR = 30, // The ignore parity flag. The parameter SHOULD be 0 if this flag is FALSE, and 1 if it is TRUE.
+ PARMRK = 31, // Mark parity and framing errors.
+ INPCK = 32, // Enable checking of parity errors.
+ ISTRIP = 33, // Strip 8th bit off characters.
+ INLCR = 34, // Map NL into CR on input.
+ IGNCR = 35, // Ignore CR on input.
+ ICRNL = 36, // Map CR to NL on input.
+ IUCLC = 37, // Translate uppercase characters to lowercase.
+ IXON = 38, // Enable output flow control.
+ IXANY = 39, // Any char will restart after stop.
+ IXOFF = 40, // Enable input flow control.
+ IMAXBEL = 41, // Ring bell on input queue full.
+ ISIG = 50, // Enable signals INTR, QUIT, [D]SUSP.
+ ICANON = 51, // Canonicalize input lines.
+ XCASE = 52, // Enable input and output of uppercase characters by preceding their lowercase equivalents with "\".
+ ECHO = 53, // Enable echoing.
+ ECHOE = 54, // Visually erase chars.
+ ECHOK = 55, // Kill character discards current line.
+ ECHONL = 56, // Echo NL even if ECHO is off.
+ NOFLSH = 57, // Don't flush after interrupt.
+ TOSTOP = 58, // Stop background jobs from output.
+ IEXTEN = 59, // Enable extensions.
+ ECHOCTL = 60, // Echo control characters as ^(Char).
+ ECHOKE = 61, // Visual erase for line kill.
+ PENDIN = 62, // Retype pending input.
+ OPOST = 70, // Enable output processing.
+ OLCUC = 71, // Convert lowercase to uppercase.
+ ONLCR = 72, // Map NL to CR-NL.
+ OCRNL = 73, // Translate carriage return to newline (output).
+ ONOCR = 74, // Translate newline to carriage return-newline (output).
+ ONLRET = 75, // Newline performs a carriage return (output).
+ CS7 = 90, // 7 bit mode.
+ CS8 = 91, // 8 bit mode.
+ PARENB = 92, // Parity enable.
+ PARODD = 93, // Odd parity, else even.
+
+ TTY_OP_ISPEED = 128, // Specifies the input baud rate in bits per second.
+ TTY_OP_OSPEED = 129 // Specifies the output baud rate in bits per second.
+ };
+
+ typedef QHash<Mode, quint32> ModeMap;
+ ModeMap modes;
+};
+
+} // namespace QSsh
+
+#endif // SSHPSEUDOTERMINAL_H
diff --git a/src/libs/ssh/sshremoteprocess.cpp b/src/libs/ssh/sshremoteprocess.cpp
new file mode 100644
index 0000000000..1bcba171fe
--- /dev/null
+++ b/src/libs/ssh/sshremoteprocess.cpp
@@ -0,0 +1,384 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshremoteprocess.h"
+#include "sshremoteprocess_p.h"
+
+#include "sshincomingpacket_p.h"
+#include "sshsendfacility_p.h"
+
+#include <botan/exceptn.h>
+
+#include <QTimer>
+
+#include <cstring>
+#include <cstdlib>
+
+/*!
+ \class QSsh::SshRemoteProcess
+
+ \brief This class implements an SSH channel for running a remote process.
+
+ Objects are created via SshConnection::createRemoteProcess.
+ The process is started via the start() member function.
+ If the process needs a pseudo terminal, you can request one
+ via requestTerminal() before calling start().
+ Note that this class does not support QIODevice's waitFor*() functions, i.e. it has
+ no synchronous mode.
+ */
+
+namespace QSsh {
+
+const struct {
+ SshRemoteProcess::Signal signalEnum;
+ const char * const signalString;
+} signalMap[] = {
+ { SshRemoteProcess::AbrtSignal, "ABRT" }, { SshRemoteProcess::AlrmSignal, "ALRM" },
+ { SshRemoteProcess::FpeSignal, "FPE" }, { SshRemoteProcess::HupSignal, "HUP" },
+ { SshRemoteProcess::IllSignal, "ILL" }, { SshRemoteProcess::IntSignal, "INT" },
+ { SshRemoteProcess::KillSignal, "KILL" }, { SshRemoteProcess::PipeSignal, "PIPE" },
+ { SshRemoteProcess::QuitSignal, "QUIT" }, { SshRemoteProcess::SegvSignal, "SEGV" },
+ { SshRemoteProcess::TermSignal, "TERM" }, { SshRemoteProcess::Usr1Signal, "USR1" },
+ { SshRemoteProcess::Usr2Signal, "USR2" }
+};
+
+SshRemoteProcess::SshRemoteProcess(const QByteArray &command, quint32 channelId,
+ Internal::SshSendFacility &sendFacility)
+ : d(new Internal::SshRemoteProcessPrivate(command, channelId, sendFacility, this))
+{
+ init();
+}
+
+SshRemoteProcess::SshRemoteProcess(quint32 channelId, Internal::SshSendFacility &sendFacility)
+ : d(new Internal::SshRemoteProcessPrivate(channelId, sendFacility, this))
+{
+ init();
+}
+
+SshRemoteProcess::~SshRemoteProcess()
+{
+ Q_ASSERT(d->channelState() == Internal::SshRemoteProcessPrivate::Inactive
+ || d->channelState() == Internal::SshRemoteProcessPrivate::CloseRequested
+ || d->channelState() == Internal::SshRemoteProcessPrivate::Closed);
+ delete d;
+}
+
+bool SshRemoteProcess::atEnd() const
+{
+ return QIODevice::atEnd() && d->data().isEmpty();
+}
+
+qint64 SshRemoteProcess::bytesAvailable() const
+{
+ return QIODevice::bytesAvailable() + d->data().count();
+}
+
+bool SshRemoteProcess::canReadLine() const
+{
+ return QIODevice::canReadLine() || d->data().contains('\n');
+}
+
+QByteArray SshRemoteProcess::readAllStandardOutput()
+{
+ return readAllFromChannel(QProcess::StandardOutput);
+}
+
+QByteArray SshRemoteProcess::readAllStandardError()
+{
+ return readAllFromChannel(QProcess::StandardError);
+}
+
+QByteArray SshRemoteProcess::readAllFromChannel(QProcess::ProcessChannel channel)
+{
+ const QProcess::ProcessChannel currentReadChannel = readChannel();
+ setReadChannel(channel);
+ const QByteArray &data = readAll();
+ setReadChannel(currentReadChannel);
+ return data;
+}
+
+void SshRemoteProcess::close()
+{
+ d->closeChannel();
+ QIODevice::close();
+}
+
+qint64 SshRemoteProcess::readData(char *data, qint64 maxlen)
+{
+ const qint64 bytesRead = qMin(qint64(d->data().count()), maxlen);
+ memcpy(data, d->data().constData(), bytesRead);
+ d->data().remove(0, bytesRead);
+ return bytesRead;
+}
+
+qint64 SshRemoteProcess::writeData(const char *data, qint64 len)
+{
+ if (isRunning()) {
+ d->sendData(QByteArray(data, len));
+ return len;
+ }
+ return 0;
+}
+
+QProcess::ProcessChannel SshRemoteProcess::readChannel() const
+{
+ return d->m_readChannel;
+}
+
+void SshRemoteProcess::setReadChannel(QProcess::ProcessChannel channel)
+{
+ d->m_readChannel = channel;
+}
+
+void SshRemoteProcess::init()
+{
+ connect(d, SIGNAL(started()), this, SIGNAL(started()),
+ Qt::QueuedConnection);
+ connect(d, SIGNAL(readyReadStandardOutput()), this, SIGNAL(readyReadStandardOutput()),
+ Qt::QueuedConnection);
+ connect(d, SIGNAL(readyRead()), this, SIGNAL(readyRead()), Qt::QueuedConnection);
+ connect(d, SIGNAL(readyReadStandardError()), this,
+ SIGNAL(readyReadStandardError()), Qt::QueuedConnection);
+ connect(d, SIGNAL(closed(int)), this, SIGNAL(closed(int)), Qt::QueuedConnection);
+}
+
+void SshRemoteProcess::addToEnvironment(const QByteArray &var, const QByteArray &value)
+{
+ if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive)
+ d->m_env << qMakePair(var, value); // Cached locally and sent on start()
+}
+
+void SshRemoteProcess::requestTerminal(const SshPseudoTerminal &terminal)
+{
+ QSSH_ASSERT_AND_RETURN(d->channelState() == Internal::SshRemoteProcessPrivate::Inactive);
+ d->m_useTerminal = true;
+ d->m_terminal = terminal;
+}
+
+void SshRemoteProcess::start()
+{
+ if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive) {
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("process start requested, channel id = %u", d->localChannelId());
+#endif
+ QIODevice::open(QIODevice::ReadWrite);
+ d->requestSessionStart();
+ }
+}
+
+void SshRemoteProcess::sendSignal(Signal signal)
+{
+ try {
+ if (isRunning()) {
+ const char *signalString = 0;
+ for (size_t i = 0; i < sizeof signalMap/sizeof *signalMap && !signalString; ++i) {
+ if (signalMap[i].signalEnum == signal)
+ signalString = signalMap[i].signalString;
+ }
+ QSSH_ASSERT_AND_RETURN(signalString);
+ d->m_sendFacility.sendChannelSignalPacket(d->remoteChannel(), signalString);
+ }
+ } catch (Botan::Exception &e) {
+ setErrorString(QString::fromAscii(e.what()));
+ d->closeChannel();
+ }
+}
+
+bool SshRemoteProcess::isRunning() const
+{
+ return d->m_procState == Internal::SshRemoteProcessPrivate::Running;
+}
+
+int SshRemoteProcess::exitCode() const { return d->m_exitCode; }
+
+SshRemoteProcess::Signal SshRemoteProcess::exitSignal() const
+{
+ return static_cast<SshRemoteProcess::Signal>(d->m_signal);
+}
+
+namespace Internal {
+
+SshRemoteProcessPrivate::SshRemoteProcessPrivate(const QByteArray &command,
+ quint32 channelId, SshSendFacility &sendFacility, SshRemoteProcess *proc)
+ : AbstractSshChannel(channelId, sendFacility),
+ m_command(command),
+ m_isShell(false),
+ m_useTerminal(false),
+ m_proc(proc)
+{
+ init();
+}
+
+SshRemoteProcessPrivate::SshRemoteProcessPrivate(quint32 channelId, SshSendFacility &sendFacility,
+ SshRemoteProcess *proc)
+ : AbstractSshChannel(channelId, sendFacility),
+ m_isShell(true),
+ m_useTerminal(true),
+ m_proc(proc)
+{
+ init();
+}
+
+void SshRemoteProcessPrivate::init()
+{
+ m_procState = NotYetStarted;
+ m_wasRunning = false;
+ m_exitCode = 0;
+ m_readChannel = QProcess::StandardOutput;
+ m_signal = SshRemoteProcess::NoSignal;
+}
+
+void SshRemoteProcessPrivate::setProcState(ProcessState newState)
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("channel: old state = %d,new state = %d", m_procState, newState);
+#endif
+ m_procState = newState;
+ if (newState == StartFailed) {
+ emit closed(SshRemoteProcess::FailedToStart);
+ } else if (newState == Running) {
+ m_wasRunning = true;
+ emit started();
+ }
+}
+
+QByteArray &SshRemoteProcessPrivate::data()
+{
+ return m_readChannel == QProcess::StandardOutput ? m_stdout : m_stderr;
+}
+
+void SshRemoteProcessPrivate::closeHook()
+{
+ if (m_wasRunning) {
+ if (m_signal != SshRemoteProcess::NoSignal)
+ emit closed(SshRemoteProcess::KilledBySignal);
+ else
+ emit closed(SshRemoteProcess::ExitedNormally);
+ }
+}
+
+void SshRemoteProcessPrivate::handleOpenSuccessInternal()
+{
+ foreach (const EnvVar &envVar, m_env) {
+ m_sendFacility.sendEnvPacket(remoteChannel(), envVar.first,
+ envVar.second);
+ }
+
+ if (m_useTerminal)
+ m_sendFacility.sendPtyRequestPacket(remoteChannel(), m_terminal);
+
+ if (m_isShell)
+ m_sendFacility.sendShellPacket(remoteChannel());
+ else
+ m_sendFacility.sendExecPacket(remoteChannel(), m_command);
+ setProcState(ExecRequested);
+ m_timeoutTimer->start(ReplyTimeout);
+}
+
+void SshRemoteProcessPrivate::handleOpenFailureInternal(const QString &reason)
+{
+ setProcState(StartFailed);
+ m_proc->setErrorString(reason);
+}
+
+void SshRemoteProcessPrivate::handleChannelSuccess()
+{
+ if (m_procState != ExecRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_MSG_CHANNEL_SUCCESS message.");
+ }
+ m_timeoutTimer->stop();
+ setProcState(Running);
+}
+
+void SshRemoteProcessPrivate::handleChannelFailure()
+{
+ if (m_procState != ExecRequested) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Unexpected SSH_MSG_CHANNEL_FAILURE message.");
+ }
+ m_timeoutTimer->stop();
+ setProcState(StartFailed);
+ closeChannel();
+}
+
+void SshRemoteProcessPrivate::handleChannelDataInternal(const QByteArray &data)
+{
+ m_stdout += data;
+ emit readyReadStandardOutput();
+ if (m_readChannel == QProcess::StandardOutput)
+ emit readyRead();
+}
+
+void SshRemoteProcessPrivate::handleChannelExtendedDataInternal(quint32 type,
+ const QByteArray &data)
+{
+ if (type != SSH_EXTENDED_DATA_STDERR) {
+ qWarning("Unknown extended data type %u", type);
+ } else {
+ m_stderr += data;
+ emit readyReadStandardError();
+ if (m_readChannel == QProcess::StandardError)
+ emit readyRead();
+ }
+}
+
+void SshRemoteProcessPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Process exiting with exit code %d", exitStatus.exitStatus);
+#endif
+ m_exitCode = exitStatus.exitStatus;
+ m_procState = Exited;
+}
+
+void SshRemoteProcessPrivate::handleExitSignal(const SshChannelExitSignal &signal)
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Exit due to signal %s", signal.signal.data());
+#endif
+
+ for (size_t i = 0; i < sizeof signalMap/sizeof *signalMap; ++i) {
+ if (signalMap[i].signalString == signal.signal) {
+ m_signal = signalMap[i].signalEnum;
+ m_procState = Exited;
+ m_proc->setErrorString(tr("Process killed by signal"));
+ return;
+ }
+ }
+
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, "Invalid signal",
+ tr("Server sent invalid signal '%1'").arg(QString::fromUtf8(signal.signal)));
+}
+
+} // namespace Internal
+} // namespace QSsh
diff --git a/src/libs/ssh/sshremoteprocess.h b/src/libs/ssh/sshremoteprocess.h
new file mode 100644
index 0000000000..16c8f7a191
--- /dev/null
+++ b/src/libs/ssh/sshremoteprocess.h
@@ -0,0 +1,130 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHREMOTECOMMAND_H
+#define SSHREMOTECOMMAND_H
+
+#include "ssh_global.h"
+
+#include <QProcess>
+#include <QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QByteArray;
+QT_END_NAMESPACE
+
+namespace QSsh {
+class SshPseudoTerminal;
+namespace Internal {
+class SshChannelManager;
+class SshRemoteProcessPrivate;
+class SshSendFacility;
+} // namespace Internal
+
+// TODO: ProcessChannel
+class QSSH_EXPORT SshRemoteProcess : public QIODevice
+{
+ Q_OBJECT
+
+ friend class Internal::SshChannelManager;
+ friend class Internal::SshRemoteProcessPrivate;
+
+public:
+ typedef QSharedPointer<SshRemoteProcess> Ptr;
+ enum ExitStatus { FailedToStart, KilledBySignal, ExitedNormally };
+ enum Signal {
+ AbrtSignal, AlrmSignal, FpeSignal, HupSignal, IllSignal, IntSignal, KillSignal, PipeSignal,
+ QuitSignal, SegvSignal, TermSignal, Usr1Signal, Usr2Signal, NoSignal
+ };
+
+ ~SshRemoteProcess();
+
+ // QIODevice stuff
+ bool atEnd() const;
+ qint64 bytesAvailable() const;
+ bool canReadLine() const;
+ void close();
+ bool isSequential() const { return true; }
+
+ QProcess::ProcessChannel readChannel() const;
+ void setReadChannel(QProcess::ProcessChannel channel);
+
+ /*
+ * Note that this is of limited value in practice, because servers are
+ * usually configured to ignore such requests for security reasons.
+ */
+ void addToEnvironment(const QByteArray &var, const QByteArray &value);
+
+ void requestTerminal(const SshPseudoTerminal &terminal);
+ void start();
+
+ bool isRunning() const;
+ int exitCode() const;
+ Signal exitSignal() const;
+
+ QByteArray readAllStandardOutput();
+ QByteArray readAllStandardError();
+
+ // Note: This is ignored by the OpenSSH server.
+ void sendSignal(Signal signal);
+ void kill() { sendSignal(KillSignal); }
+
+signals:
+ void started();
+
+ void readyReadStandardOutput();
+ void readyReadStandardError();
+
+ /*
+ * Parameter is of type ExitStatus, but we use int because of
+ * signal/slot awkwardness (full namespace required).
+ */
+ void closed(int exitStatus);
+
+private:
+ SshRemoteProcess(const QByteArray &command, quint32 channelId,
+ Internal::SshSendFacility &sendFacility);
+ SshRemoteProcess(quint32 channelId, Internal::SshSendFacility &sendFacility);
+
+ // QIODevice stuff
+ qint64 readData(char *data, qint64 maxlen);
+ qint64 writeData(const char *data, qint64 len);
+
+ void init();
+ QByteArray readAllFromChannel(QProcess::ProcessChannel channel);
+
+ Internal::SshRemoteProcessPrivate *d;
+};
+
+} // namespace QSsh
+
+#endif // SSHREMOTECOMMAND_H
diff --git a/src/libs/ssh/sshremoteprocess_p.h b/src/libs/ssh/sshremoteprocess_p.h
new file mode 100644
index 0000000000..1e64c307e2
--- /dev/null
+++ b/src/libs/ssh/sshremoteprocess_p.h
@@ -0,0 +1,114 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHREMOTEPROCESS_P_H
+#define SSHREMOTEPROCESS_P_H
+
+#include "sshpseudoterminal.h"
+
+#include "sshchannel_p.h"
+
+#include <QList>
+#include <QPair>
+#include <QProcess>
+
+namespace QSsh {
+class SshRemoteProcess;
+
+namespace Internal {
+class SshSendFacility;
+
+class SshRemoteProcessPrivate : public AbstractSshChannel
+{
+ Q_OBJECT
+ friend class QSsh::SshRemoteProcess;
+public:
+ enum ProcessState {
+ NotYetStarted, ExecRequested, StartFailed, Running, Exited
+ };
+
+ virtual void handleChannelSuccess();
+ virtual void handleChannelFailure();
+
+ virtual void closeHook();
+
+ QByteArray &data();
+
+signals:
+ void started();
+ void readyRead();
+ void readyReadStandardOutput();
+ void readyReadStandardError();
+ void closed(int exitStatus);
+
+private:
+ SshRemoteProcessPrivate(const QByteArray &command, quint32 channelId,
+ SshSendFacility &sendFacility, SshRemoteProcess *proc);
+ SshRemoteProcessPrivate(quint32 channelId, SshSendFacility &sendFacility,
+ SshRemoteProcess *proc);
+
+ virtual void handleOpenSuccessInternal();
+ virtual void handleOpenFailureInternal(const QString &reason);
+ virtual void handleChannelDataInternal(const QByteArray &data);
+ virtual void handleChannelExtendedDataInternal(quint32 type,
+ const QByteArray &data);
+ virtual void handleExitStatus(const SshChannelExitStatus &exitStatus);
+ virtual void handleExitSignal(const SshChannelExitSignal &signal);
+
+ void init();
+ void setProcState(ProcessState newState);
+
+ QProcess::ProcessChannel m_readChannel;
+
+ ProcessState m_procState;
+ bool m_wasRunning;
+ int m_signal;
+ int m_exitCode;
+
+ const QByteArray m_command;
+ const bool m_isShell;
+
+ typedef QPair<QByteArray, QByteArray> EnvVar;
+ QList<EnvVar> m_env;
+ bool m_useTerminal;
+ SshPseudoTerminal m_terminal;
+
+ QByteArray m_stdout;
+ QByteArray m_stderr;
+
+ SshRemoteProcess *m_proc;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHREMOTEPROCESS_P_H
diff --git a/src/libs/ssh/sshremoteprocessrunner.cpp b/src/libs/ssh/sshremoteprocessrunner.cpp
new file mode 100644
index 0000000000..5af53eab4c
--- /dev/null
+++ b/src/libs/ssh/sshremoteprocessrunner.cpp
@@ -0,0 +1,269 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshremoteprocessrunner.h"
+
+#include "sshconnectionmanager.h"
+#include "sshpseudoterminal.h"
+
+
+/*!
+ \class QSsh::SshRemoteProcessRunner
+
+ \brief Convenience class for running a remote process over an SSH connection.
+*/
+
+namespace QSsh {
+namespace Internal {
+namespace {
+enum State { Inactive, Connecting, Connected, ProcessRunning };
+} // anonymous namespace
+
+class SshRemoteProcessRunnerPrivate
+{
+public:
+ SshRemoteProcessRunnerPrivate() : m_state(Inactive) {}
+
+ SshRemoteProcess::Ptr m_process;
+ SshConnection::Ptr m_connection;
+ bool m_runInTerminal;
+ SshPseudoTerminal m_terminal;
+ QByteArray m_command;
+ QSsh::SshError m_lastConnectionError;
+ QString m_lastConnectionErrorString;
+ SshRemoteProcess::ExitStatus m_exitStatus;
+ SshRemoteProcess::Signal m_exitSignal;
+ int m_exitCode;
+ QString m_processErrorString;
+ State m_state;
+};
+
+} // namespace Internal
+
+using namespace Internal;
+
+SshRemoteProcessRunner::SshRemoteProcessRunner(QObject *parent)
+ : QObject(parent), d(new SshRemoteProcessRunnerPrivate)
+{
+}
+
+SshRemoteProcessRunner::~SshRemoteProcessRunner()
+{
+ disconnect();
+ setState(Inactive);
+ delete d;
+}
+
+void SshRemoteProcessRunner::run(const QByteArray &command,
+ const SshConnectionParameters &sshParams)
+{
+ QSSH_ASSERT_AND_RETURN(d->m_state == Inactive);
+
+ d->m_runInTerminal = false;
+ runInternal(command, sshParams);
+}
+
+void SshRemoteProcessRunner::runInTerminal(const QByteArray &command,
+ const SshPseudoTerminal &terminal, const SshConnectionParameters &sshParams)
+{
+ d->m_terminal = terminal;
+ d->m_runInTerminal = true;
+ runInternal(command, sshParams);
+}
+
+void SshRemoteProcessRunner::runInternal(const QByteArray &command,
+ const SshConnectionParameters &sshParams)
+{
+ setState(Connecting);
+
+ d->m_lastConnectionError = SshNoError;
+ d->m_lastConnectionErrorString.clear();
+ d->m_processErrorString.clear();
+ d->m_exitSignal = SshRemoteProcess::NoSignal;
+ d->m_exitCode = -1;
+ d->m_command = command;
+ d->m_connection = SshConnectionManager::instance().acquireConnection(sshParams);
+ connect(d->m_connection.data(), SIGNAL(error(QSsh::SshError)),
+ SLOT(handleConnectionError(QSsh::SshError)));
+ connect(d->m_connection.data(), SIGNAL(disconnected()), SLOT(handleDisconnected()));
+ if (d->m_connection->state() == SshConnection::Connected) {
+ handleConnected();
+ } else {
+ connect(d->m_connection.data(), SIGNAL(connected()), SLOT(handleConnected()));
+ if (d->m_connection->state() == SshConnection::Unconnected)
+ d->m_connection->connectToHost();
+ }
+}
+
+void SshRemoteProcessRunner::handleConnected()
+{
+ QSSH_ASSERT_AND_RETURN(d->m_state == Connecting);
+ setState(Connected);
+
+ d->m_process = d->m_connection->createRemoteProcess(d->m_command);
+ connect(d->m_process.data(), SIGNAL(started()), SLOT(handleProcessStarted()));
+ connect(d->m_process.data(), SIGNAL(closed(int)), SLOT(handleProcessFinished(int)));
+ connect(d->m_process.data(), SIGNAL(readyReadStandardOutput()), SLOT(handleStdout()));
+ connect(d->m_process.data(), SIGNAL(readyReadStandardError()), SLOT(handleStderr()));
+ if (d->m_runInTerminal)
+ d->m_process->requestTerminal(d->m_terminal);
+ d->m_process->start();
+}
+
+void SshRemoteProcessRunner::handleConnectionError(QSsh::SshError error)
+{
+ d->m_lastConnectionError = error;
+ d->m_lastConnectionErrorString = d->m_connection->errorString();
+ handleDisconnected();
+ emit connectionError();
+}
+
+void SshRemoteProcessRunner::handleDisconnected()
+{
+ QSSH_ASSERT_AND_RETURN(d->m_state == Connecting || d->m_state == Connected
+ || d->m_state == ProcessRunning);
+ setState(Inactive);
+}
+
+void SshRemoteProcessRunner::handleProcessStarted()
+{
+ QSSH_ASSERT_AND_RETURN(d->m_state == Connected);
+
+ setState(ProcessRunning);
+ emit processStarted();
+}
+
+void SshRemoteProcessRunner::handleProcessFinished(int exitStatus)
+{
+ d->m_exitStatus = static_cast<SshRemoteProcess::ExitStatus>(exitStatus);
+ switch (d->m_exitStatus) {
+ case SshRemoteProcess::FailedToStart:
+ QSSH_ASSERT_AND_RETURN(d->m_state == Connected);
+ break;
+ case SshRemoteProcess::KilledBySignal:
+ QSSH_ASSERT_AND_RETURN(d->m_state == ProcessRunning);
+ d->m_exitSignal = d->m_process->exitSignal();
+ break;
+ case SshRemoteProcess::ExitedNormally:
+ QSSH_ASSERT_AND_RETURN(d->m_state == ProcessRunning);
+ d->m_exitCode = d->m_process->exitCode();
+ break;
+ default:
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Impossible exit status.");
+ }
+ d->m_processErrorString = d->m_process->errorString();
+ setState(Inactive);
+ emit processClosed(exitStatus);
+}
+
+void SshRemoteProcessRunner::handleStdout()
+{
+ emit processOutputAvailable(d->m_process->readAllStandardOutput());
+}
+
+void SshRemoteProcessRunner::handleStderr()
+{
+ emit processErrorOutputAvailable(d->m_process->readAllStandardError());
+}
+
+void SshRemoteProcessRunner::setState(int newState)
+{
+ if (d->m_state == newState)
+ return;
+
+ d->m_state = static_cast<State>(newState);
+ if (d->m_state == Inactive) {
+ if (d->m_process) {
+ disconnect(d->m_process.data(), 0, this, 0);
+ d->m_process->close();
+ d->m_process.clear();
+ }
+ if (d->m_connection) {
+ disconnect(d->m_connection.data(), 0, this, 0);
+ SshConnectionManager::instance().releaseConnection(d->m_connection);
+ d->m_connection.clear();
+ }
+ }
+}
+
+QByteArray SshRemoteProcessRunner::command() const { return d->m_command; }
+SshError SshRemoteProcessRunner::lastConnectionError() const { return d->m_lastConnectionError; }
+QString SshRemoteProcessRunner::lastConnectionErrorString() const {
+ return d->m_lastConnectionErrorString;
+}
+
+bool SshRemoteProcessRunner::isProcessRunning() const
+{
+ return d->m_process && d->m_process->isRunning();
+}
+
+SshRemoteProcess::ExitStatus SshRemoteProcessRunner::processExitStatus() const
+{
+ QSSH_ASSERT(!isProcessRunning());
+ return d->m_exitStatus;
+}
+
+SshRemoteProcess::Signal SshRemoteProcessRunner::processExitSignal() const
+{
+ QSSH_ASSERT(processExitStatus() == SshRemoteProcess::KilledBySignal);
+ return d->m_exitSignal;
+}
+
+int SshRemoteProcessRunner::processExitCode() const
+{
+ QSSH_ASSERT(processExitStatus() == SshRemoteProcess::ExitedNormally);
+ return d->m_exitCode;
+}
+
+QString SshRemoteProcessRunner::processErrorString() const
+{
+ return d->m_processErrorString;
+}
+
+void SshRemoteProcessRunner::writeDataToProcess(const QByteArray &data)
+{
+ QSSH_ASSERT(isProcessRunning());
+ d->m_process->write(data);
+}
+
+void SshRemoteProcessRunner::sendSignalToProcess(SshRemoteProcess::Signal signal)
+{
+ QSSH_ASSERT(isProcessRunning());
+ d->m_process->sendSignal(signal);
+}
+
+void SshRemoteProcessRunner::cancel()
+{
+ setState(Inactive);
+}
+
+} // namespace QSsh
diff --git a/src/libs/ssh/sshremoteprocessrunner.h b/src/libs/ssh/sshremoteprocessrunner.h
new file mode 100644
index 0000000000..57d755bc0f
--- /dev/null
+++ b/src/libs/ssh/sshremoteprocessrunner.h
@@ -0,0 +1,94 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHREMOTEPROCESSRUNNER_H
+#define SSHREMOTEPROCESSRUNNER_H
+
+#include "sshconnection.h"
+#include "sshremoteprocess.h"
+
+namespace QSsh {
+namespace Internal {
+class SshRemoteProcessRunnerPrivate;
+} // namespace Internal
+
+class QSSH_EXPORT SshRemoteProcessRunner : public QObject
+{
+ Q_OBJECT
+
+public:
+ SshRemoteProcessRunner(QObject *parent = 0);
+ ~SshRemoteProcessRunner();
+
+ void run(const QByteArray &command, const SshConnectionParameters &sshParams);
+ void runInTerminal(const QByteArray &command, const SshPseudoTerminal &terminal,
+ const SshConnectionParameters &sshParams);
+ QByteArray command() const;
+
+ QSsh::SshError lastConnectionError() const;
+ QString lastConnectionErrorString() const;
+
+ bool isProcessRunning() const;
+ void writeDataToProcess(const QByteArray &data);
+ void sendSignalToProcess(SshRemoteProcess::Signal signal); // No effect with OpenSSH server.
+ void cancel(); // Does not stop remote process, just frees SSH-related process resources.
+ SshRemoteProcess::ExitStatus processExitStatus() const;
+ SshRemoteProcess::Signal processExitSignal() const;
+ int processExitCode() const;
+ QString processErrorString() const;
+
+private slots:
+ void handleConnected();
+ void handleConnectionError(QSsh::SshError error);
+ void handleDisconnected();
+ void handleProcessStarted();
+ void handleProcessFinished(int exitStatus);
+ void handleStdout();
+ void handleStderr();
+
+signals:
+ void connectionError();
+ void processStarted();
+ void processOutputAvailable(const QByteArray &output);
+ void processErrorOutputAvailable(const QByteArray &output);
+ void processClosed(int exitStatus); // values are of type SshRemoteProcess::ExitStatus
+
+private:
+ void runInternal(const QByteArray &command, const QSsh::SshConnectionParameters &sshParams);
+ void setState(int newState);
+
+ Internal::SshRemoteProcessRunnerPrivate * const d;
+};
+
+} // namespace QSsh
+
+#endif // SSHREMOTEPROCESSRUNNER_H
diff --git a/src/libs/ssh/sshsendfacility.cpp b/src/libs/ssh/sshsendfacility.cpp
new file mode 100644
index 0000000000..2797dce1df
--- /dev/null
+++ b/src/libs/ssh/sshsendfacility.cpp
@@ -0,0 +1,222 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "sshsendfacility_p.h"
+
+#include "sshkeyexchange_p.h"
+#include "sshoutgoingpacket_p.h"
+
+#include <QTcpSocket>
+
+namespace QSsh {
+namespace Internal {
+
+SshSendFacility::SshSendFacility(QTcpSocket *socket)
+ : m_clientSeqNr(0), m_socket(socket),
+ m_outgoingPacket(m_encrypter, m_clientSeqNr)
+{
+}
+
+void SshSendFacility::sendPacket()
+{
+#ifdef CREATOR_SSH_DEBUG
+ qDebug("Sending packet, client seq nr is %u", m_clientSeqNr);
+#endif
+ if (m_socket->isValid()
+ && m_socket->state() == QAbstractSocket::ConnectedState) {
+ m_socket->write(m_outgoingPacket.rawData());
+ ++m_clientSeqNr;
+ }
+}
+
+void SshSendFacility::reset()
+{
+ m_clientSeqNr = 0;
+ m_encrypter.clearKeys();
+}
+
+void SshSendFacility::recreateKeys(const SshKeyExchange &keyExchange)
+{
+ m_encrypter.recreateKeys(keyExchange);
+}
+
+void SshSendFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
+{
+ m_encrypter.createAuthenticationKey(privKeyFileContents);
+}
+
+QByteArray SshSendFacility::sendKeyExchangeInitPacket()
+{
+ const QByteArray &payLoad = m_outgoingPacket.generateKeyExchangeInitPacket();
+ sendPacket();
+ return payLoad;
+}
+
+void SshSendFacility::sendKeyDhInitPacket(const Botan::BigInt &e)
+{
+ m_outgoingPacket.generateKeyDhInitPacket(e);
+ sendPacket();
+}
+
+void SshSendFacility::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::sendIgnorePacket()
+{
+ m_outgoingPacket.generateIgnorePacket();
+ sendPacket();
+}
+
+void SshSendFacility::sendInvalidPacket()
+{
+ m_outgoingPacket.generateInvalidMessagePacket();
+ sendPacket();
+}
+
+void SshSendFacility::sendSessionPacket(quint32 channelId, quint32 windowSize,
+ quint32 maxPacketSize)
+{
+ m_outgoingPacket.generateSessionPacket(channelId, windowSize,
+ maxPacketSize);
+ sendPacket();
+}
+
+void SshSendFacility::sendPtyRequestPacket(quint32 remoteChannel,
+ const SshPseudoTerminal &terminal)
+{
+ m_outgoingPacket.generatePtyRequestPacket(remoteChannel, terminal);
+ sendPacket();
+}
+
+void SshSendFacility::sendEnvPacket(quint32 remoteChannel,
+ const QByteArray &var, const QByteArray &value)
+{
+ m_outgoingPacket.generateEnvPacket(remoteChannel, var, value);
+ sendPacket();
+}
+
+void SshSendFacility::sendExecPacket(quint32 remoteChannel,
+ const QByteArray &command)
+{
+ m_outgoingPacket.generateExecPacket(remoteChannel, command);
+ sendPacket();
+}
+
+void SshSendFacility::sendShellPacket(quint32 remoteChannel)
+{
+ m_outgoingPacket.generateShellPacket(remoteChannel);
+ sendPacket();
+}
+
+void SshSendFacility::sendSftpPacket(quint32 remoteChannel)
+{
+ m_outgoingPacket.generateSftpPacket(remoteChannel);
+ sendPacket();
+}
+
+void SshSendFacility::sendWindowAdjustPacket(quint32 remoteChannel,
+ quint32 bytesToAdd)
+{
+ m_outgoingPacket.generateWindowAdjustPacket(remoteChannel, bytesToAdd);
+ sendPacket();
+}
+
+void SshSendFacility::sendChannelDataPacket(quint32 remoteChannel,
+ const QByteArray &data)
+{
+ m_outgoingPacket.generateChannelDataPacket(remoteChannel, data);
+ sendPacket();
+}
+
+void SshSendFacility::sendChannelSignalPacket(quint32 remoteChannel,
+ const QByteArray &signalName)
+{
+ m_outgoingPacket.generateChannelSignalPacket(remoteChannel, signalName);
+ sendPacket();
+}
+
+void SshSendFacility::sendChannelEofPacket(quint32 remoteChannel)
+{
+ m_outgoingPacket.generateChannelEofPacket(remoteChannel);
+ sendPacket();
+}
+
+void SshSendFacility::sendChannelClosePacket(quint32 remoteChannel)
+{
+ m_outgoingPacket.generateChannelClosePacket(remoteChannel);
+ sendPacket();
+}
+
+} // namespace Internal
+} // namespace QSsh
diff --git a/src/libs/ssh/sshsendfacility_p.h b/src/libs/ssh/sshsendfacility_p.h
new file mode 100644
index 0000000000..2801d2a454
--- /dev/null
+++ b/src/libs/ssh/sshsendfacility_p.h
@@ -0,0 +1,101 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef SSHCONNECTIONOUTSTATE_P_H
+#define SSHCONNECTIONOUTSTATE_P_H
+
+#include "sshcryptofacility_p.h"
+#include "sshoutgoingpacket_p.h"
+
+QT_BEGIN_NAMESPACE
+class QTcpSocket;
+QT_END_NAMESPACE
+
+
+namespace QSsh {
+class SshPseudoTerminal;
+
+namespace Internal {
+class SshKeyExchange;
+
+class SshSendFacility
+{
+public:
+ SshSendFacility(QTcpSocket *socket);
+ void reset();
+ void recreateKeys(const SshKeyExchange &keyExchange);
+ void createAuthenticationKey(const QByteArray &privKeyFileContents);
+
+ QByteArray 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 sendIgnorePacket();
+ void sendInvalidPacket();
+ void sendSessionPacket(quint32 channelId, quint32 windowSize,
+ quint32 maxPacketSize);
+ void sendPtyRequestPacket(quint32 remoteChannel,
+ const SshPseudoTerminal &terminal);
+ void sendEnvPacket(quint32 remoteChannel, const QByteArray &var,
+ const QByteArray &value);
+ void sendExecPacket(quint32 remoteChannel, const QByteArray &command);
+ void sendShellPacket(quint32 remoteChannel);
+ void sendSftpPacket(quint32 remoteChannel);
+ void sendWindowAdjustPacket(quint32 remoteChannel, quint32 bytesToAdd);
+ void sendChannelDataPacket(quint32 remoteChannel, const QByteArray &data);
+ void sendChannelSignalPacket(quint32 remoteChannel,
+ const QByteArray &signalName);
+ void sendChannelEofPacket(quint32 remoteChannel);
+ void sendChannelClosePacket(quint32 remoteChannel);
+ quint32 nextClientSeqNr() const { return m_clientSeqNr; }
+
+private:
+ void sendPacket();
+
+ quint32 m_clientSeqNr;
+ SshEncryptionFacility m_encrypter;
+ QTcpSocket *m_socket;
+ SshOutgoingPacket m_outgoingPacket;
+};
+
+} // namespace Internal
+} // namespace QSsh
+
+#endif // SSHCONNECTIONOUTSTATE_P_H