diff options
Diffstat (limited to 'src/libs/ssh')
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>&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>&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 &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>&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>&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 |