First build (#3)

* Client app template and ui
* ui fixes
* ui fixes
* Submodule update
* Tap drivers added
* Openvpn added
* Openvpn for macos added
* ui fixes
* Router class added
* Refactoring
* Add installer
* openvpnconfigurator
* updated gitignore
* easyrsa
* QtSsh
* OpenVPN files for Windows
* WIP: main
* General improvements and bug fixes
* server scripts and connection classes
* openvpn script fixes
* General improvements and bug fixes
* Server scripts fixes
* Filter ssh output
* Fix typo
* ui fixes
* Frameless window
* ui fixes for macos
* Refactoring
This commit is contained in:
pokamest 2021-01-06 21:58:00 +03:00 committed by GitHub
commit f0e5fbeda0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
288 changed files with 94603 additions and 13 deletions

16
.gitignore vendored
View file

@ -1,17 +1,8 @@
# User settings
*.user
macOSPackage/
# C++ objects and libs
*.slo
*.lo
*.o
*.a
*.la
*.lai
*.so
*.dll
*.dylib
AmneziaVPN.dmg
AmneziaVPN.exe
# Qt-es
/.qmake.cache
@ -47,4 +38,5 @@ CMakeLists.txt.user*
*.*~
# Certificates
*.p12
*.p12

27
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,27 @@
variables:
GIT_STRATEGY: clone
stages:
- build
build-windows:
stage: build
tags:
- windows
script:
- cmd.exe /k "deploy\windows-env.bat && cd deploy && windows.bat"
artifacts:
name: artifacts-windows
paths:
- AmneziaVPN.exe
build-macos:
stage: build
tags:
- macos
script:
- cd deploy && ./macos.sh
artifacts:
name: artifacts-macos
paths:
- AmneziaVPN.dmg

@ -1 +0,0 @@
Subproject commit 542ee3adbc1b663d8764ea8b219f802beaf8523c

2
AmneziaVPN.pro Normal file
View file

@ -0,0 +1,2 @@
TEMPLATE = subdirs
SUBDIRS = client service platform

2
README.md Normal file
View file

@ -0,0 +1,2 @@
# Amnezia

50
client/3rd/QtSsh/.gitignore vendored Normal file
View file

@ -0,0 +1,50 @@
# User settings
*.user
macOSPackage/
# C++ objects and libs
*.slo
*.lo
*.o
*.a
*.la
*.lai
*.so
*.dll
*.dylib
# Qt-es
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
qrc_*.cpp
ui_*.h
Makefile*
*build-*
# QtCreator
*.autosave
# QtCtreator Qml
*.qmlproject.user
*.qmlproject.user.*
# QtCtreator CMake
CMakeLists.txt.user*
# MACOS files
.DS_Store
._.DS_Store
._*
# tmp files
*.*~
# Certificates
*.p12

View file

@ -0,0 +1,3 @@
load(qt_build_config)
MODULE_VERSION = 4.3.1

View file

@ -0,0 +1 @@
load(qt_parts)

View file

@ -0,0 +1,46 @@
# QSsh
this project is base on Qt-creator-open-source-4.3.1
project is at
`http://code.qt.io/cgit/qt-creator/qt-creator.git/`
you can download code zip at
`http://download.qt.io/official_releases/qtcreator/4.3/4.3.1/`
## Getting Started
> * For linux user, if your Qt is installed through package manager tools such "apt-get", make sure that you have installed the Qt5 develop package *qtbase5-private-dev*
### Usage(1): Use QtSsh as Qt5's addon module
#### Building the module
> **Note**: Perl is needed in this step.
* Download the source code.
* Put the source code in any directory you like
* Go to top directory of the project in a terminal and run
```
qmake
make
make install
```
The library, the header files, and others will be installed to your system.
#### Import the module
` QT += ssh`
#### Include Headerfile
` #include<QtSsh/sshconnection.h>`
#### Close qtc.ssh log
` QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false")`

View file

@ -0,0 +1,2 @@
TEMPLATE = subdirs
SUBDIRS = gitlab

View file

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>Qml/Main.qml</file>
</qresource>
</RCC>

View file

@ -0,0 +1,51 @@
import QtQuick 2.0
import QtQuick.Controls 2.0
import Ssh 1.0
Item {
width: 1024
height: 768
TextField {
id: input
x: 104
y: 44
width: 482
height: 40
implicitWidth: 200
selectByMouse: true
text: "https://www.zhihu.com"
}
function get(url) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
console.log(xhr.responseXML, xhr.responseText.toString())
} else if (xhr.readyState === XMLHttpRequest) {
}
}
xhr.open('GET', url)
xhr.send()
}
Button {
x: 627
y: 44
text: "get"
onClicked: {
get(input.text)
}
}
Button {
id: button
x: 104
y: 170
text: qsTr("Ssh")
onClicked: ssh.connectToHost()
}
Ssh {
id: ssh
}
}

View file

@ -0,0 +1,17 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include "Ssh.hpp"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<Ssh> ("Ssh", 1, 0, "Ssh");
QQuickView view;
view.setSource(QUrl("qrc:/Qml/Main.qml"));
view.show();
return app.exec();
}

View file

@ -0,0 +1,61 @@
#include "Ssh.hpp"
#include <QDir>
#include <QDebug>
#include <QLoggingCategory>
Ssh::Ssh(QObject *parent) : QObject(parent) {
//关掉qtc.ssh中的各种打印信息
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
mParams.host="ftb.autoio.org";
mParams.userName = "ftb";
mParams.port = 11122;
mParams.privateKeyFile = QDir::homePath() + QStringLiteral("/.ssh/id_rsa");
mParams.timeout = 5;
mParams.authenticationType = SshConnectionParameters::AuthenticationTypePublicKey;
mParams.options = SshIgnoreDefaultProxy;
mParams.hostKeyCheckingMode = SshHostKeyCheckingNone;
mConnections = std::make_shared<SshConnection>(mParams);
connect(mConnections.get(), &SshConnection::error, [&](QSsh::SshError){
qWarning() << "Error: " << mConnections->errorString();
});
connect(mConnections.get(), &SshConnection::connected, [&](){
qWarning() << "Connected";
create();
});
connect(mConnections.get(), &SshConnection::disconnected, [](){
qWarning() << "Disconnected";
});
connect(mConnections.get(), &SshConnection::dataAvailable, [](const QString &message){
qWarning() << "Message: " << message;
});
}
void Ssh::connectToHost() {
mConnections->connectToHost();
}
void Ssh::create() {
mRemoteProcess = mConnections->createRemoteProcess(QString::fromLatin1("/bin/ls -a").toUtf8());
if (!mRemoteProcess) {
qWarning() << QLatin1String("Error: UnmRemoteProcess SSH connection creates remote process.");
return;
}
connect(mRemoteProcess.data(), &SshRemoteProcess::started, [&](){
qWarning() << "started";
});
connect(mRemoteProcess.data(), &SshRemoteProcess::readyReadStandardOutput, [&](){
qWarning() << "StandardOutput";
qWarning() << QString::fromLatin1(mRemoteProcess->readAllStandardOutput()).split('\n');
});
connect(mRemoteProcess.data(), &SshRemoteProcess::readyReadStandardError, [&](){
qWarning() << "StandardError" << mRemoteProcess->readAllStandardError();
});
connect(mRemoteProcess.data(), &SshRemoteProcess::closed, [&](int exitStatus){
qWarning() << "Exit" << exitStatus;
});
mRemoteProcess->start();
}

View file

@ -0,0 +1,26 @@
#pragma once
#include <memory>
#include <cstdlib>
#include <functional>
#include <QObject>
#include <QtSsh/sshconnection.h>
#include <QtSsh/sshremoteprocess.h>
using namespace QSsh;
class Ssh : public QObject {
Q_OBJECT
public:
explicit Ssh(QObject *parent = nullptr);
Q_INVOKABLE void connectToHost();
void create();
private:
SshConnectionParameters mParams;
std::shared_ptr<SshConnection> mConnections;
QSharedPointer<SshRemoteProcess> mRemoteProcess;
};

View file

@ -0,0 +1,29 @@
QT += quick network ssh
CONFIG += c++11
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
#DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += Src/Main.cpp \
Src/Ssh.cpp
RESOURCES += Qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
HEADERS += \
Src/Ssh.hpp

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
INCLUDEPATH *= $$PWD/..
HEADERS += $$PWD/botan.h
SOURCES += $$PWD/botan.cpp
CONFIG += exceptions
DEPENDPATH += .
DEFINES += BOTAN_DLL=
unix:DEFINES += BOTAN_TARGET_OS_HAS_GETTIMEOFDAY BOTAN_HAS_ALLOC_MMAP \
BOTAN_HAS_ENTROPY_SRC_DEV_RANDOM BOTAN_HAS_ENTROPY_SRC_EGD BOTAN_HAS_ENTROPY_SRC_FTW \
BOTAN_HAS_ENTROPY_SRC_UNIX BOTAN_HAS_MUTEX_PTHREAD BOTAN_HAS_PIPE_UNIXFD_IO
*linux*:DEFINES += BOTAN_TARGET_OS_IS_LINUX BOTAN_TARGET_OS_HAS_CLOCK_GETTIME \
BOTAN_TARGET_OS_HAS_DLOPEN BOTAN_TARGET_OS_HAS_GMTIME_R BOTAN_TARGET_OS_HAS_POSIX_MLOCK \
BOTAN_HAS_DYNAMICALLY_LOADED_ENGINE BOTAN_HAS_DYNAMIC_LOADER
macx:DEFINES += BOTAN_TARGET_OS_IS_DARWIN
*g++*:DEFINES += BOTAN_BUILD_COMPILER_IS_GCC
*clang*:DEFINES += BOTAN_BUILD_COMPILER_IS_CLANG
*icc*:DEFINES += BOTAN_BUILD_COMPILER_IS_INTEL
CONFIG(x86_64):DEFINES += BOTAN_TARGET_ARCH_IS_X86_64
win32 {
DEFINES += BOTAN_TARGET_OS_IS_WINDOWS \
BOTAN_TARGET_OS_HAS_LOADLIBRARY BOTAN_TARGET_OS_HAS_WIN32_GET_SYSTEMTIME \
BOTAN_TARGET_OS_HAS_WIN32_VIRTUAL_LOCK \
BOTAN_HAS_ENTROPY_SRC_CAPI BOTAN_HAS_ENTROPY_SRC_WIN32 \
BOTAN_HAS_MUTEX_WIN32
msvc {
QMAKE_CXXFLAGS_EXCEPTIONS_ON = -EHs
QMAKE_CXXFLAGS += -wd4251 -wd4290 -wd4250 -wd4297 -wd4267 -wd4334
DEFINES += BOTAN_BUILD_COMPILER_IS_MSVC BOTAN_TARGET_OS_HAS_GMTIME_S _SCL_SECURE_NO_WARNINGS
} else {
QMAKE_CFLAGS += -fpermissive -finline-functions -Wno-long-long
QMAKE_CXXFLAGS += -fpermissive -finline-functions -Wno-long-long
}
LIBS += -ladvapi32 -luser32
}
unix:*-g++* {
QMAKE_CFLAGS += -fPIC -fpermissive -finline-functions -Wno-long-long
QMAKE_CXXFLAGS += -fPIC -fpermissive -finline-functions -Wno-long-long
}
linux*|freebsd* {
LIBS += -lrt $$QMAKE_LIBS_DYNLOAD
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
.. _license:
.. highlight:: none
License
========================================
Botan (http://botan.randombit.net/) is distributed under these terms::
Copyright (C) 1999-2011 Jack Lloyd
2001 Peter J Jones
2004-2007 Justin Karneges
2004 Vaclav Ovsik
2005 Matthew Gregan
2005-2006 Matt Johnston
2006 Luca Piccarreta
2007 Yves Jerschow
2007-2008 FlexSecure GmbH
2007-2008 Technische Universitat Darmstadt
2007-2008 Falko Strenzke
2007-2008 Martin Doering
2007 Manuel Hartl
2007 Christoph Ludwig
2007 Patrick Sona
2010 Olivier de Gaalon
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions, and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTOR(S) BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,15 @@
Botan 1.10.2, 2012-06-17
http://botan.randombit.net/
Botan is a C++ class library for performing a wide variety of
cryptographic operations. It is released under the 2 clause BSD
license; see doc/license.txt for the specifics. You can file bugs in
Bugzilla (http://bugs.randombit.net/) or by sending a report to the
botan-devel mailing list. More information about the mailing list is
at http://lists.randombit.net/mailman/listinfo/botan-devel/
You can find documentation online at http://botan.randombit.net/ as
well as in the doc directory in the distribution. Several examples can
be found in doc/examples as well.
Jack Lloyd (lloyd@randombit.net)

View file

@ -0,0 +1,3 @@
TEMPLATE = subdirs
SUBDIRS = ssh

View file

@ -0,0 +1,972 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sftpchannel.h"
#include "sftpchannel_p.h"
#include "sshexception_p.h"
#include "sshincomingpacket_p.h"
#include "sshlogging_p.h"
#include "sshsendfacility_p.h"
#include <QDir>
#include <QFile>
/*!
\class QSsh::SftpChannel
\brief The SftpChannel 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, &Internal::SftpChannelPrivate::initialized,
this, &SftpChannel::initialized, Qt::QueuedConnection);
connect(d, &Internal::SftpChannelPrivate::channelError,
this, &SftpChannel::channelError, Qt::QueuedConnection);
connect(d, &Internal::SftpChannelPrivate::dataAvailable,
this, &SftpChannel::dataAvailable, Qt::QueuedConnection);
connect(d, &Internal::SftpChannelPrivate::fileInfoAvailable,
this, &SftpChannel::fileInfoAvailable, Qt::QueuedConnection);
connect(d, &Internal::SftpChannelPrivate::finished,
this, &SftpChannel::finished, Qt::QueuedConnection);
connect(d, &Internal::SftpChannelPrivate::closed,
this, &SftpChannel::closed, Qt::QueuedConnection);
}
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;
qCDebug(sshLog, "sftp subsystem initialized");
sendData(m_outgoingPacket.generateInit(ProtocolVersion).rawData());
m_sftpState = InitSent;
}
void SftpChannelPrivate::handleChannelFailure()
{
if (channelState() == CloseRequested)
return;
if (m_sftpState != SubsystemRequested) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected SSH_MSG_CHANNEL_FAILURE packet.");
}
emit channelError(tr("Server could not start SFTP subsystem."));
closeChannel();
}
void SftpChannelPrivate::handleChannelDataInternal(const QByteArray &data)
{
if (channelState() == CloseRequested)
return;
m_incomingData += data;
m_incomingPacket.consumeData(m_incomingData);
while (m_incomingPacket.isComplete()) {
handleCurrentPacket();
m_incomingPacket.clear();
m_incomingPacket.consumeData(m_incomingData);
}
}
void SftpChannelPrivate::handleChannelExtendedDataInternal(quint32 type,
const QByteArray &data)
{
qCWarning(sshLog, "Unexpected extended data '%s' of type %d on SFTP channel.",
data.data(), type);
}
void SftpChannelPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
{
qCDebug(sshLog, "Remote SFTP service exited with exit code %d", exitStatus.exitStatus);
if (channelState() == CloseRequested || channelState() == Closed)
return;
emit channelError(tr("The SFTP server finished unexpectedly with exit code %1.")
.arg(exitStatus.exitStatus));
// Note: According to the specs, the server must close the channel after this happens,
// but OpenSSH doesn't do that, so we need to initiate the closing procedure ourselves.
closeChannel();
}
void SftpChannelPrivate::handleExitSignal(const SshChannelExitSignal &signal)
{
emit channelError(tr("The SFTP server crashed: %1.").arg(signal.error));
closeChannel(); // See above.
}
void SftpChannelPrivate::handleCurrentPacket()
{
qCDebug(sshLog, "Handling SFTP packet of type %d", m_incomingPacket.type());
switch (m_incomingPacket.type()) {
case SSH_FXP_VERSION:
handleServerVersion();
break;
case SSH_FXP_HANDLE:
handleHandle();
break;
case SSH_FXP_NAME:
handleName();
break;
case SSH_FXP_STATUS:
handleStatus();
break;
case SSH_FXP_DATA:
handleReadData();
break;
case SSH_FXP_ATTRS:
handleAttrs();
break;
default:
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected packet.",
tr("Unexpected packet of type %1.").arg(m_incomingPacket.type()));
}
}
void SftpChannelPrivate::handleServerVersion()
{
checkChannelActive();
if (m_sftpState != InitSent) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected SSH_FXP_VERSION packet.");
}
qCDebug(sshLog, "sftp init received");
const quint32 serverVersion = m_incomingPacket.extractServerVersion();
if (serverVersion != ProtocolVersion) {
emit channelError(tr("Protocol version mismatch: Expected %1, got %2")
.arg(serverVersion).arg(ProtocolVersion));
closeChannel();
} else {
m_sftpState = Initialized;
emit initialized();
}
}
void SftpChannelPrivate::handleHandle()
{
const SftpHandleResponse &response = m_incomingPacket.asHandleResponse();
JobMap::Iterator it = lookupJob(response.requestId);
const QSharedPointer<AbstractSftpOperationWithHandle> job
= it.value().dynamicCast<AbstractSftpOperationWithHandle>();
if (job.isNull()) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected SSH_FXP_HANDLE packet.");
}
if (job->state != AbstractSftpOperationWithHandle::OpenRequested) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected SSH_FXP_HANDLE packet.");
}
job->remoteHandle = response.handle;
job->state = AbstractSftpOperationWithHandle::Open;
switch (it.value()->type()) {
case AbstractSftpOperation::ListDir:
handleLsHandle(it);
break;
case AbstractSftpOperation::CreateFile:
handleCreateFileHandle(it);
break;
case AbstractSftpOperation::Download:
handleGetHandle(it);
break;
case AbstractSftpOperation::UploadFile:
handlePutHandle(it);
break;
default:
Q_ASSERT(!"Oh no, I forgot to handle an SFTP operation type!");
}
}
void SftpChannelPrivate::handleLsHandle(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();
qCDebug(sshLog, "%s: status = %d", Q_FUNC_INFO, response.status);
JobMap::Iterator it = lookupJob(response.requestId);
switch (it.value()->type()) {
case AbstractSftpOperation::ListDir:
handleLsStatus(it, response);
break;
case AbstractSftpOperation::Download:
handleGetStatus(it, response);
break;
case AbstractSftpOperation::UploadFile:
handlePutStatus(it, response);
break;
case AbstractSftpOperation::MakeDir:
handleMkdirStatus(it, response);
break;
case AbstractSftpOperation::StatFile:
case AbstractSftpOperation::RmDir:
case AbstractSftpOperation::Rm:
case AbstractSftpOperation::Rename:
case AbstractSftpOperation::CreateFile:
case AbstractSftpOperation::CreateLink:
handleStatusGeneric(it, response);
break;
}
}
void SftpChannelPrivate::handleStatusGeneric(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>();
QSharedPointer<SftpUploadDir> parentJob = op->parentJob;
if (parentJob == SftpUploadDir::Ptr()) {
handleStatusGeneric(it, response);
return;
}
if (parentJob->hasError) {
m_jobs.erase(it);
return;
}
typedef QMap<SftpMakeDir::Ptr, SftpUploadDir::Dir>::Iterator DirIt;
DirIt dirIt = parentJob->mkdirsInProgress.find(op);
Q_ASSERT(dirIt != parentJob->mkdirsInProgress.end());
const QString &remoteDir = dirIt.value().remoteDir;
if (response.status == SSH_FX_OK) {
emit dataAvailable(parentJob->jobId,
tr("Created remote directory \"%1\".").arg(remoteDir));
} else if (response.status == SSH_FX_FAILURE) {
emit dataAvailable(parentJob->jobId,
tr("Remote directory \"%1\" already exists.").arg(remoteDir));
} else {
parentJob->setError();
emit finished(parentJob->jobId,
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, parentJob));
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)) {
parentJob->setError();
emit finished(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, parentJob));
createJob(uploadFileOp);
parentJob->uploadsInProgress.append(uploadFileOp);
}
parentJob->mkdirsInProgress.erase(dirIt);
if (parentJob->mkdirsInProgress.isEmpty()
&& parentJob->uploadsInProgress.isEmpty())
emit finished(parentJob->jobId);
m_jobs.erase(it);
}
void SftpChannelPrivate::handleLsStatus(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 to retrieve information on the remote file ('stat' failed).")));
sendTransferCloseHandle(op, response.requestId);
} else {
if ((response.status != SSH_FX_EOF || response.requestId != op->eofId)
&& !op->hasError)
reportRequestError(op, 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()
{
for (JobMap::ConstIterator it = m_jobs.constBegin(); it != m_jobs.constEnd(); ++it)
emit finished(it.key(), tr("SFTP channel closed unexpectedly."));
m_jobs.clear();
m_incomingData.clear();
m_incomingPacket.clear();
emit closed();
}
void SftpChannelPrivate::handleOpenSuccessInternal()
{
qCDebug(sshLog, "SFTP session started");
m_sendFacility.sendSftpPacket(remoteChannel());
m_sftpState = SubsystemRequested;
}
void SftpChannelPrivate::handleOpenFailureInternal(const QString &reason)
{
if (channelState() != SessionRequested) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
}
emit channelError(tr("Server could not start session: %1").arg(reason));
}
void SftpChannelPrivate::sendReadRequest(const SftpDownload::Ptr &job,
quint32 requestId)
{
Q_ASSERT(job->eofId == SftpInvalidJob);
sendData(m_outgoingPacket.generateReadFile(job->remoteHandle, job->offset,
AbstractSftpPacket::MaxDataSize, requestId).rawData());
job->offsets[requestId] = job->offset;
job->offset += AbstractSftpPacket::MaxDataSize;
if (job->offset >= job->fileSize)
job->eofId = requestId;
}
void SftpChannelPrivate::reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job,
const 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

View file

@ -0,0 +1,103 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "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 channelError(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

View file

@ -0,0 +1,124 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "sftpdefs.h"
#include "sftpincomingpacket_p.h"
#include "sftpoperation_p.h"
#include "sftpoutgoingpacket_p.h"
#include "sshchannel_p.h"
#include <QByteArray>
#include <QMap>
namespace QSsh {
class SftpChannel;
namespace Internal {
class SftpChannelPrivate : public AbstractSshChannel
{
Q_OBJECT
friend class QSsh::SftpChannel;
public:
enum SftpState { Inactive, SubsystemRequested, InitSent, Initialized };
signals:
void initialized();
void channelError(const QString &reason);
void closed();
void finished(QSsh::SftpJobId job, const 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 handleChannelSuccess();
virtual void handleChannelFailure();
virtual void handleOpenSuccessInternal();
virtual void handleOpenFailureInternal(const QString &reason);
virtual void handleChannelDataInternal(const QByteArray &data);
virtual void handleChannelExtendedDataInternal(quint32 type,
const QByteArray &data);
virtual void handleExitStatus(const SshChannelExitStatus &exitStatus);
virtual void handleExitSignal(const SshChannelExitSignal &signal);
virtual void closeHook();
void handleCurrentPacket();
void handleServerVersion();
void handleHandle();
void handleStatus();
void handleName();
void handleReadData();
void handleAttrs();
void 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

View file

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

View file

@ -0,0 +1,59 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "ssh_global.h"
#include <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

View file

@ -0,0 +1,383 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "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 *sshConnection;
SftpChannel::Ptr sftpChannel;
QString rootDirectory;
SftpFileNode *rootNode;
SftpJobId statJobId;
DirNodeHash lsOps;
QList<SftpJobId> externalJobs;
};
} // namespace Internal
using namespace Internal;
SftpFileSystemModel::SftpFileSystemModel(QObject *parent)
: QAbstractItemModel(parent), d(new SftpFileSystemModelPrivate)
{
d->sshConnection = 0;
d->rootDirectory = QLatin1Char('/');
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 = QSsh::acquireConnection(sshParams);
connect(d->sshConnection, &SshConnection::error,
this, &SftpFileSystemModel::handleSshConnectionFailure);
if (d->sshConnection->state() == SshConnection::Connected) {
handleSshConnectionEstablished();
return;
}
connect(d->sshConnection, &SshConnection::connected,
this, &SftpFileSystemModel::handleSshConnectionEstablished);
if (d->sshConnection->state() == SshConnection::Unconnected)
d->sshConnection->connectToHost();
}
void SftpFileSystemModel::setRootDirectory(const QString &path)
{
beginResetModel();
d->rootDirectory = path;
delete d->rootNode;
d->rootNode = 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(":/utils/images/unknownfile.png");
case FileTypeDirectory:
return QIcon(":/utils/images/dir.png");
case FileTypeUnknown:
return QIcon(":/utils/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, 0, this, 0);
QSsh::releaseConnection(d->sshConnection);
d->sshConnection = 0;
}
delete d->rootNode;
d->rootNode = 0;
}
void SftpFileSystemModel::handleSshConnectionFailure()
{
emit connectionError(d->sshConnection->errorString());
beginResetModel();
shutDown();
endResetModel();
}
void SftpFileSystemModel::handleSftpChannelInitialized()
{
connect(d->sftpChannel.data(),
&SftpChannel::fileInfoAvailable,
this, &SftpFileSystemModel::handleFileInfo);
connect(d->sftpChannel.data(), &SftpChannel::finished,
this, &SftpFileSystemModel::handleSftpJobFinished);
statRootDirectory();
}
void SftpFileSystemModel::handleSshConnectionEstablished()
{
d->sftpChannel = d->sshConnection->createSftpChannel();
connect(d->sftpChannel.data(), &SftpChannel::initialized,
this, &SftpFileSystemModel::handleSftpChannelInitialized);
connect(d->sftpChannel.data(), &SftpChannel::channelError,
this, &SftpFileSystemModel::handleSftpChannelError);
d->sftpChannel->initialize();
}
void SftpFileSystemModel::handleSftpChannelError(const QString &reason)
{
emit connectionError(reason);
beginResetModel();
shutDown();
endResetModel();
}
void SftpFileSystemModel::handleFileInfo(SftpJobId jobId, const QList<SftpFileInfo> &fileInfoList)
{
if (jobId == d->statJobId) {
QSSH_ASSERT_AND_RETURN(!d->rootNode);
beginInsertRows(QModelIndex(), 0, 0);
d->rootNode = new SftpDirNode;
d->rootNode->path = d->rootDirectory;
d->rootNode->fileInfo = fileInfoList.first();
d->rootNode->fileInfo.name = d->rootDirectory == QLatin1String("/")
? d->rootDirectory : QFileInfo(d->rootDirectory).fileName();
endInsertRows();
return;
}
SftpDirNode * const parentNode = d->lsOps.value(jobId);
QSSH_ASSERT_AND_RETURN(parentNode);
QList<SftpFileInfo> filteredList;
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

View file

@ -0,0 +1,100 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "sftpdefs.h"
#include "ssh_global.h"
#include <QAbstractItemModel>
namespace QSsh {
class SshConnectionParameters;
namespace Internal { class SftpFileSystemModelPrivate; }
// Very simple read-only model. Symbolic links are not followed.
class QSSH_EXPORT SftpFileSystemModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit SftpFileSystemModel(QObject *parent = 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);
// Use this to get the full path of a file or directory.
static const int PathRole = Qt::UserRole;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
signals:
/*
* E.g. "Permission denied". Note that this can happen without direct user intervention,
* due to e.g. the view calling rowCount() on a non-readable directory. This signal should
* therefore not result in a message box or similar, since it might occur very often.
*/
void sftpOperationFailed(const QString &errorMessage);
/*
* This error is not recoverable. The model will not have any content after
* the signal has been emitted.
*/
void connectionError(const QString &errorMessage);
// Success <=> error.isEmpty().
void sftpOperationFinished(QSsh::SftpJobId, const QString &error);
private:
void handleSshConnectionEstablished();
void handleSshConnectionFailure();
void handleSftpChannelInitialized();
void handleSftpChannelError(const QString &reason);
void handleFileInfo(QSsh::SftpJobId jobId, const QList<QSsh::SftpFileInfo> &fileInfoList);
void handleSftpJobFinished(QSsh::SftpJobId jobId, const QString &errorMessage);
int columnCount(const QModelIndex &parent = QModelIndex()) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &child) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
void statRootDirectory();
void shutDown();
Internal::SftpFileSystemModelPrivate * const d;
};
} // namespace QSsh;

View file

@ -0,0 +1,217 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sftpincomingpacket_p.h"
#include "sshexception_p.h"
#include "sshlogging_p.h"
#include "sshpacketparser_p.h"
namespace QSsh {
namespace Internal {
SftpIncomingPacket::SftpIncomingPacket() : m_length(0)
{
}
void SftpIncomingPacket::consumeData(QByteArray &newData)
{
qCDebug(sshLog, "%s: current data size = %d, new data size = %d", Q_FUNC_INFO,
m_data.size(), newData.size());
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 (const SshPacketParseException &) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Invalid SSH_FXP_VERSION packet.");
}
}
SftpHandleResponse SftpIncomingPacket::asHandleResponse() const
{
Q_ASSERT(isComplete());
Q_ASSERT(type() == SSH_FXP_HANDLE);
try {
SftpHandleResponse response;
quint32 offset = RequestIdOffset;
response.requestId = SshPacketParser::asUint32(m_data, &offset);
response.handle = SshPacketParser::asString(m_data, &offset);
return response;
} catch (const SshPacketParseException &) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Invalid SSH_FXP_HANDLE packet");
}
}
SftpStatusResponse SftpIncomingPacket::asStatusResponse() const
{
Q_ASSERT(isComplete());
Q_ASSERT(type() == SSH_FXP_STATUS);
try {
SftpStatusResponse response;
quint32 offset = RequestIdOffset;
response.requestId = SshPacketParser::asUint32(m_data, &offset);
response.status = static_cast<SftpStatusCode>(SshPacketParser::asUint32(m_data, &offset));
response.errorString = SshPacketParser::asUserString(m_data, &offset);
response.language = SshPacketParser::asString(m_data, &offset);
return response;
} catch (const SshPacketParseException &) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Invalid SSH_FXP_STATUS packet.");
}
}
SftpNameResponse SftpIncomingPacket::asNameResponse() const
{
Q_ASSERT(isComplete());
Q_ASSERT(type() == SSH_FXP_NAME);
try {
SftpNameResponse response;
quint32 offset = RequestIdOffset;
response.requestId = SshPacketParser::asUint32(m_data, &offset);
const quint32 count = SshPacketParser::asUint32(m_data, &offset);
for (quint32 i = 0; i < count; ++i)
response.files << asFile(offset);
return response;
} catch (const SshPacketParseException &) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Invalid SSH_FXP_NAME packet.");
}
}
SftpDataResponse SftpIncomingPacket::asDataResponse() const
{
Q_ASSERT(isComplete());
Q_ASSERT(type() == SSH_FXP_DATA);
try {
SftpDataResponse response;
quint32 offset = RequestIdOffset;
response.requestId = SshPacketParser::asUint32(m_data, &offset);
response.data = SshPacketParser::asString(m_data, &offset);
return response;
} catch (const SshPacketParseException &) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Invalid SSH_FXP_DATA packet.");
}
}
SftpAttrsResponse SftpIncomingPacket::asAttrsResponse() const
{
Q_ASSERT(isComplete());
Q_ASSERT(type() == SSH_FXP_ATTRS);
try {
SftpAttrsResponse response;
quint32 offset = RequestIdOffset;
response.requestId = SshPacketParser::asUint32(m_data, &offset);
response.attrs = asFileAttributes(offset);
return response;
} catch (const SshPacketParseException &) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Invalid SSH_FXP_ATTRS packet.");
}
}
SftpFile SftpIncomingPacket::asFile(quint32 &offset) const
{
SftpFile file;
file.fileName
= QString::fromUtf8(SshPacketParser::asString(m_data, &offset));
file.longName
= QString::fromUtf8(SshPacketParser::asString(m_data, &offset));
file.attributes = asFileAttributes(offset);
return file;
}
SftpFileAttributes SftpIncomingPacket::asFileAttributes(quint32 &offset) const
{
SftpFileAttributes attributes;
const quint32 flags = SshPacketParser::asUint32(m_data, &offset);
attributes.sizePresent = flags & SSH_FILEXFER_ATTR_SIZE;
attributes.timesPresent = flags & SSH_FILEXFER_ATTR_ACMODTIME;
attributes.uidAndGidPresent = flags & SSH_FILEXFER_ATTR_UIDGID;
attributes.permissionsPresent = flags & SSH_FILEXFER_ATTR_PERMISSIONS;
if (attributes.sizePresent)
attributes.size = SshPacketParser::asUint64(m_data, &offset);
if (attributes.uidAndGidPresent) {
attributes.uid = SshPacketParser::asUint32(m_data, &offset);
attributes.gid = SshPacketParser::asUint32(m_data, &offset);
}
if (attributes.permissionsPresent)
attributes.permissions = SshPacketParser::asUint32(m_data, &offset);
if (attributes.timesPresent) {
attributes.atime = SshPacketParser::asUint32(m_data, &offset);
attributes.mtime = SshPacketParser::asUint32(m_data, &offset);
}
if (flags & SSH_FILEXFER_ATTR_EXTENDED) {
const quint32 count = SshPacketParser::asUint32(m_data, &offset);
for (quint32 i = 0; i < count; ++i) {
SshPacketParser::asString(m_data, &offset);
SshPacketParser::asString(m_data, &offset);
}
}
return attributes;
}
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,104 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "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

View file

@ -0,0 +1,220 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sftpoperation_p.h"
#include "sftpoutgoingpacket_p.h"
#include <QFile>
namespace QSsh {
namespace Internal {
AbstractSftpOperation::AbstractSftpOperation(SftpJobId jobId) : jobId(jobId)
{
}
AbstractSftpOperation::~AbstractSftpOperation() { }
SftpStatFile::SftpStatFile(SftpJobId jobId, const QString &path)
: AbstractSftpOperation(jobId), path(path)
{
}
SftpOutgoingPacket &SftpStatFile::initialPacket(SftpOutgoingPacket &packet)
{
return packet.generateStat(path, jobId);
}
SftpMakeDir::SftpMakeDir(SftpJobId jobId, const QString &path,
const SftpUploadDir::Ptr &parentJob)
: AbstractSftpOperation(jobId), parentJob(parentJob), remoteDir(path)
{
}
SftpOutgoingPacket &SftpMakeDir::initialPacket(SftpOutgoingPacket &packet)
{
return packet.generateMkDir(remoteDir, jobId);
}
SftpRmDir::SftpRmDir(SftpJobId id, const QString &path)
: AbstractSftpOperation(id), remoteDir(path)
{
}
SftpOutgoingPacket &SftpRmDir::initialPacket(SftpOutgoingPacket &packet)
{
return packet.generateRmDir(remoteDir, jobId);
}
SftpRm::SftpRm(SftpJobId jobId, const QString &path)
: AbstractSftpOperation(jobId), remoteFile(path) {}
SftpOutgoingPacket &SftpRm::initialPacket(SftpOutgoingPacket &packet)
{
return packet.generateRm(remoteFile, jobId);
}
SftpRename::SftpRename(SftpJobId jobId, const QString &oldPath,
const QString &newPath)
: AbstractSftpOperation(jobId), oldPath(oldPath), newPath(newPath)
{
}
SftpOutgoingPacket &SftpRename::initialPacket(SftpOutgoingPacket &packet)
{
return packet.generateRename(oldPath, newPath, jobId);
}
SftpCreateLink::SftpCreateLink(SftpJobId jobId, const QString &filePath, const QString &target)
: AbstractSftpOperation(jobId), filePath(filePath), target(target)
{
}
SftpOutgoingPacket &SftpCreateLink::initialPacket(SftpOutgoingPacket &packet)
{
return packet.generateCreateLink(filePath, target, jobId);
}
AbstractSftpOperationWithHandle::AbstractSftpOperationWithHandle(SftpJobId jobId,
const QString &remotePath)
: AbstractSftpOperation(jobId),
remotePath(remotePath), state(Inactive), hasError(false)
{
}
AbstractSftpOperationWithHandle::~AbstractSftpOperationWithHandle() { }
SftpListDir::SftpListDir(SftpJobId jobId, const QString &path)
: 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

View file

@ -0,0 +1,244 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "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 id, const QString &path);
virtual Type type() const { return RmDir; }
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
const QString remoteDir;
};
struct SftpRm : public AbstractSftpOperation
{
typedef QSharedPointer<SftpRm> Ptr;
SftpRm(SftpJobId jobId, const QString &path);
virtual Type type() const { return Rm; }
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
const QString remoteFile;
};
struct SftpRename : public AbstractSftpOperation
{
typedef QSharedPointer<SftpRename> Ptr;
SftpRename(SftpJobId jobId, const QString &oldPath, const QString &newPath);
virtual Type type() const { return Rename; }
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
const QString oldPath;
const QString newPath;
};
struct SftpCreateLink : public AbstractSftpOperation
{
typedef QSharedPointer<SftpCreateLink> Ptr;
SftpCreateLink(SftpJobId jobId, const QString &filePath, const QString &target);
virtual Type type() const { return CreateLink; }
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
const QString filePath;
const QString target;
};
struct AbstractSftpOperationWithHandle : public AbstractSftpOperation
{
typedef QSharedPointer<AbstractSftpOperationWithHandle> Ptr;
enum State { Inactive, OpenRequested, Open, CloseRequested };
AbstractSftpOperationWithHandle(SftpJobId jobId, const QString &remotePath);
~AbstractSftpOperationWithHandle();
const QString remotePath;
QByteArray remoteHandle;
State state;
bool hasError;
};
struct SftpListDir : public AbstractSftpOperationWithHandle
{
typedef QSharedPointer<SftpListDir> Ptr;
SftpListDir(SftpJobId jobId, const QString &path);
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

View file

@ -0,0 +1,220 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sftpoutgoingpacket_p.h"
#include "sshlogging_p.h"
#include "sshpacket_p.h"
#include <QtEndian>
#include <limits>
namespace QSsh {
namespace Internal {
namespace {
const quint32 DefaultAttributes = 0;
const quint32 SSH_FXF_READ = 0x00000001;
const quint32 SSH_FXF_WRITE = 0x00000002;
const quint32 SSH_FXF_APPEND = 0x00000004;
const quint32 SSH_FXF_CREAT = 0x00000008;
const quint32 SSH_FXF_TRUNC = 0x00000010;
const quint32 SSH_FXF_EXCL = 0x00000020;
}
SftpOutgoingPacket::SftpOutgoingPacket()
{
}
SftpOutgoingPacket &SftpOutgoingPacket::generateInit(quint32 version)
{
return init(SSH_FXP_INIT, 0).appendInt(version).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateStat(const QString &path, quint32 requestId)
{
return init(SSH_FXP_LSTAT, requestId).appendString(path).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateOpenDir(const QString &path,
quint32 requestId)
{
return init(SSH_FXP_OPENDIR, requestId).appendString(path).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateReadDir(const QByteArray &handle,
quint32 requestId)
{
return init(SSH_FXP_READDIR, requestId).appendString(handle).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateCloseHandle(const QByteArray &handle,
quint32 requestId)
{
return init(SSH_FXP_CLOSE, requestId).appendString(handle).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateMkDir(const QString &path,
quint32 requestId)
{
return init(SSH_FXP_MKDIR, requestId).appendString(path)
.appendInt(DefaultAttributes).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateRmDir(const QString &path,
quint32 requestId)
{
return init(SSH_FXP_RMDIR, requestId).appendString(path).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateRm(const QString &path,
quint32 requestId)
{
return init(SSH_FXP_REMOVE, requestId).appendString(path).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateRename(const QString &oldPath,
const QString &newPath, quint32 requestId)
{
return init(SSH_FXP_RENAME, requestId).appendString(oldPath)
.appendString(newPath).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFileForWriting(const QString &path,
SftpOverwriteMode mode, quint32 permissions, quint32 requestId)
{
QList<quint32> attributes;
if (permissions != DefaultPermissions)
attributes << SSH_FILEXFER_ATTR_PERMISSIONS << permissions;
else
attributes << DefaultAttributes;
return generateOpenFile(path, Write, mode, attributes, requestId);
}
SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFileForReading(const QString &path,
quint32 requestId)
{
// Note: Overwrite mode is irrelevant and will be ignored.
return generateOpenFile(path, Read, SftpSkipExisting, QList<quint32>() << DefaultAttributes,
requestId);
}
SftpOutgoingPacket &SftpOutgoingPacket::generateReadFile(const QByteArray &handle,
quint64 offset, quint32 length, quint32 requestId)
{
return init(SSH_FXP_READ, requestId).appendString(handle).appendInt64(offset)
.appendInt(length).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateFstat(const QByteArray &handle,
quint32 requestId)
{
return init(SSH_FXP_FSTAT, requestId).appendString(handle).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateWriteFile(const QByteArray &handle,
quint64 offset, const QByteArray &data, quint32 requestId)
{
return init(SSH_FXP_WRITE, requestId).appendString(handle)
.appendInt64(offset).appendString(data).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateCreateLink(const QString &filePath,
const QString &target, quint32 requestId)
{
return init(SSH_FXP_SYMLINK, requestId).appendString(filePath).appendString(target).finalize();
}
SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFile(const QString &path,
OpenType openType, SftpOverwriteMode mode, const QList<quint32> &attributes, quint32 requestId)
{
quint32 pFlags = 0;
switch (openType) {
case Read:
pFlags = SSH_FXF_READ;
break;
case Write:
pFlags = SSH_FXF_WRITE | SSH_FXF_CREAT;
switch (mode) {
case SftpOverwriteExisting: pFlags |= SSH_FXF_TRUNC; break;
case SftpAppendToExisting: pFlags |= SSH_FXF_APPEND; break;
case SftpSkipExisting: pFlags |= SSH_FXF_EXCL; break;
}
break;
}
init(SSH_FXP_OPEN, requestId).appendString(path).appendInt(pFlags);
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);
qCDebug(sshLog, "Generating SFTP packet of type %d with request id %u", type, requestId);
}
return *this;
}
SftpOutgoingPacket &SftpOutgoingPacket::appendInt(quint32 val)
{
m_data.append(AbstractSshPacket::encodeInt(val));
return *this;
}
SftpOutgoingPacket &SftpOutgoingPacket::appendInt64(quint64 value)
{
m_data.append(AbstractSshPacket::encodeInt(value));
return *this;
}
SftpOutgoingPacket &SftpOutgoingPacket::appendString(const QString &string)
{
m_data.append(AbstractSshPacket::encodeString(string.toUtf8()));
return *this;
}
SftpOutgoingPacket &SftpOutgoingPacket::appendString(const QByteArray &string)
{
m_data += AbstractSshPacket::encodeString(string);
return *this;
}
SftpOutgoingPacket &SftpOutgoingPacket::finalize()
{
AbstractSshPacket::setLengthField(m_data);
return *this;
}
const quint32 SftpOutgoingPacket::DefaultPermissions = std::numeric_limits<quint32>::max();
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,84 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "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

View file

@ -0,0 +1,49 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "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

View file

@ -0,0 +1,109 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QByteArray>
#include <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

View file

@ -0,0 +1,81 @@
QT += gui network widgets
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
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 \
$$PWD/sshinit.cpp \
$$PWD/sshdirecttcpiptunnel.cpp \
$$PWD/sshlogging.cpp \
$$PWD/sshhostkeydatabase.cpp \
$$PWD/sshtcpipforwardserver.cpp \
$$PWD/sshtcpiptunnel.cpp \
$$PWD/sshforwardedtcpiptunnel.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 \
$$PWD/sshdirecttcpiptunnel_p.h \
$$PWD/sshinit_p.h \
$$PWD/sshdirecttcpiptunnel.h \
$$PWD/sshlogging_p.h \
$$PWD/sshhostkeydatabase.h \
$$PWD/sshtcpipforwardserver.h \
$$PWD/sshtcpipforwardserver_p.h \
$$PWD/sshtcpiptunnel_p.h \
$$PWD/sshforwardedtcpiptunnel.h \
$$PWD/sshforwardedtcpiptunnel_p.h
FORMS += $$PWD/sshkeycreationdialog.ui

View file

@ -0,0 +1,8 @@
TARGET = QtSsh
load(qt_module)
DEFINES += QTCSSH_LIBRARY
include($$PWD/ssh.pri)
include($$PWD/../botan/botan.pri)

View file

@ -0,0 +1 @@
QTC_LIB_NAME = QtcSsh

View file

@ -0,0 +1,41 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QtGlobal>
//#if defined(QTCSSH_LIBRARY)
//# define QSSH_EXPORT Q_DECL_EXPORT
//#else
//# define QSSH_EXPORT Q_DECL_IMPORT
//#endif
#define QSSH_EXPORT
#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)

View file

@ -0,0 +1,132 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "sshcapabilities_p.h"
#include "sshexception_p.h"
#include <botan/botan.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()), static_cast<int>(v.size()));
}
inline const char *botanKeyExchangeAlgoName(const QByteArray &rfcAlgoName)
{
if (rfcAlgoName == SshCapabilities::DiffieHellmanGroup1Sha1)
return "modp/ietf/1024";
if (rfcAlgoName == SshCapabilities::DiffieHellmanGroup14Sha1)
return "modp/ietf/2048";
if (rfcAlgoName == SshCapabilities::EcdhNistp256)
return "secp256r1";
if (rfcAlgoName == SshCapabilities::EcdhNistp384)
return "secp384r1";
if (rfcAlgoName == SshCapabilities::EcdhNistp521)
return "secp521r1";
throw SshClientException(SshInternalError, SSH_TR("Unexpected key exchange algorithm \"%1\"")
.arg(QString::fromLatin1(rfcAlgoName)));
}
inline const char *botanCryptAlgoName(const QByteArray &rfcAlgoName)
{
if (rfcAlgoName == SshCapabilities::CryptAlgoAes128Cbc
|| rfcAlgoName == SshCapabilities::CryptAlgoAes128Ctr) {
return "AES-128";
}
if (rfcAlgoName == SshCapabilities::CryptAlgo3DesCbc
|| rfcAlgoName == SshCapabilities::CryptAlgo3DesCtr) {
return "TripleDES";
}
if (rfcAlgoName == SshCapabilities::CryptAlgoAes192Ctr) {
return "AES-192";
}
if (rfcAlgoName == SshCapabilities::CryptAlgoAes256Ctr) {
return "AES-256";
}
throw SshClientException(SshInternalError, SSH_TR("Unexpected cipher \"%1\"")
.arg(QString::fromLatin1(rfcAlgoName)));
}
inline const char *botanEmsaAlgoName(const QByteArray &rfcAlgoName)
{
if (rfcAlgoName == SshCapabilities::PubKeyDss)
return "EMSA1(SHA-1)";
if (rfcAlgoName == SshCapabilities::PubKeyRsa)
return "EMSA3(SHA-1)";
if (rfcAlgoName == SshCapabilities::PubKeyEcdsa256)
return "EMSA1_BSI(SHA-256)";
if (rfcAlgoName == SshCapabilities::PubKeyEcdsa384)
return "EMSA1_BSI(SHA-384)";
if (rfcAlgoName == SshCapabilities::PubKeyEcdsa521)
return "EMSA1_BSI(SHA-512)";
throw SshClientException(SshInternalError, SSH_TR("Unexpected host key algorithm \"%1\"")
.arg(QString::fromLatin1(rfcAlgoName)));
}
inline const char *botanHMacAlgoName(const QByteArray &rfcAlgoName)
{
if (rfcAlgoName == SshCapabilities::HMacSha1)
return "SHA-1";
if (rfcAlgoName == SshCapabilities::HMacSha256)
return "SHA-256";
if (rfcAlgoName == SshCapabilities::HMacSha384)
return "SHA-384";
if (rfcAlgoName == SshCapabilities::HMacSha512)
return "SHA-512";
throw SshClientException(SshInternalError, SSH_TR("Unexpected hashing algorithm \"%1\"")
.arg(QString::fromLatin1(rfcAlgoName)));
}
inline quint32 botanHMacKeyLen(const QByteArray &rfcAlgoName)
{
if (rfcAlgoName == SshCapabilities::HMacSha1)
return 20;
if (rfcAlgoName == SshCapabilities::HMacSha256)
return 32;
if (rfcAlgoName == SshCapabilities::HMacSha384)
return 48;
if (rfcAlgoName == SshCapabilities::HMacSha512)
return 64;
throw SshClientException(SshInternalError, SSH_TR("Unexpected hashing algorithm \"%1\"")
.arg(QString::fromLatin1(rfcAlgoName)));
}
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,170 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "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 QByteArray SshCapabilities::EcdhKexNamePrefix("ecdh-sha2-nistp");
const QByteArray SshCapabilities::EcdhNistp256 = EcdhKexNamePrefix + "256";
const QByteArray SshCapabilities::EcdhNistp384 = EcdhKexNamePrefix + "384";
const QByteArray SshCapabilities::EcdhNistp521 = EcdhKexNamePrefix + "521";
const QList<QByteArray> SshCapabilities::KeyExchangeMethods = QList<QByteArray>()
<< SshCapabilities::EcdhNistp256
<< SshCapabilities::EcdhNistp384
<< SshCapabilities::EcdhNistp521
<< SshCapabilities::DiffieHellmanGroup1Sha1
<< SshCapabilities::DiffieHellmanGroup14Sha1;
const QByteArray SshCapabilities::PubKeyDss("ssh-dss");
const QByteArray SshCapabilities::PubKeyRsa("ssh-rsa");
const QByteArray SshCapabilities::PubKeyEcdsaPrefix("ecdsa-sha2-nistp");
const QByteArray SshCapabilities::PubKeyEcdsa256 = SshCapabilities::PubKeyEcdsaPrefix + "256";
const QByteArray SshCapabilities::PubKeyEcdsa384 = SshCapabilities::PubKeyEcdsaPrefix + "384";
const QByteArray SshCapabilities::PubKeyEcdsa521 = SshCapabilities::PubKeyEcdsaPrefix + "521";
const QList<QByteArray> SshCapabilities::PublicKeyAlgorithms = QList<QByteArray>()
<< SshCapabilities::PubKeyEcdsa256
<< SshCapabilities::PubKeyEcdsa384
<< SshCapabilities::PubKeyEcdsa521
<< SshCapabilities::PubKeyRsa
<< SshCapabilities::PubKeyDss;
const QByteArray SshCapabilities::CryptAlgo3DesCbc("3des-cbc");
const QByteArray SshCapabilities::CryptAlgo3DesCtr("3des-ctr");
const QByteArray SshCapabilities::CryptAlgoAes128Cbc("aes128-cbc");
const QByteArray SshCapabilities::CryptAlgoAes128Ctr("aes128-ctr");
const QByteArray SshCapabilities::CryptAlgoAes192Ctr("aes192-ctr");
const QByteArray SshCapabilities::CryptAlgoAes256Ctr("aes256-ctr");
const QList<QByteArray> SshCapabilities::EncryptionAlgorithms
= QList<QByteArray>() << SshCapabilities::CryptAlgoAes256Ctr
<< SshCapabilities::CryptAlgoAes192Ctr
<< SshCapabilities::CryptAlgoAes128Ctr
<< SshCapabilities::CryptAlgo3DesCtr
<< SshCapabilities::CryptAlgoAes128Cbc
<< SshCapabilities::CryptAlgo3DesCbc;
const QByteArray SshCapabilities::HMacSha1("hmac-sha1");
const QByteArray SshCapabilities::HMacSha196("hmac-sha1-96");
const QByteArray SshCapabilities::HMacSha256("hmac-sha2-256");
const QByteArray SshCapabilities::HMacSha384("hmac-sha2-384");
const QByteArray SshCapabilities::HMacSha512("hmac-sha2-512");
const QList<QByteArray> SshCapabilities::MacAlgorithms
= QList<QByteArray>() /* << SshCapabilities::HMacSha196 */
<< SshCapabilities::HMacSha256
<< SshCapabilities::HMacSha384
<< SshCapabilities::HMacSha512
<< SshCapabilities::HMacSha1;
const QList<QByteArray> SshCapabilities::CompressionAlgorithms
= QList<QByteArray>() << "none";
const QByteArray SshCapabilities::SshConnectionService("ssh-connection");
QList<QByteArray> SshCapabilities::commonCapabilities(const QList<QByteArray> &myCapabilities,
const QList<QByteArray> &serverCapabilities)
{
QList<QByteArray> capabilities;
foreach (const QByteArray &myCapability, myCapabilities) {
if (serverCapabilities.contains(myCapability))
capabilities << myCapability;
}
if (!capabilities.isEmpty())
return capabilities;
throw SshServerException(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
"Server and client capabilities do not match.",
QCoreApplication::translate("SshConnection",
"Server and client capabilities don't match. "
"Client list was: %1.\nServer list was %2.")
.arg(QString::fromLocal8Bit(listAsByteArray(myCapabilities).data()))
.arg(QString::fromLocal8Bit(listAsByteArray(serverCapabilities).data())));
}
QByteArray SshCapabilities::findBestMatch(const QList<QByteArray> &myCapabilities,
const QList<QByteArray> &serverCapabilities)
{
return commonCapabilities(myCapabilities, serverCapabilities).first();
}
int SshCapabilities::ecdsaIntegerWidthInBytes(const QByteArray &ecdsaAlgo)
{
if (ecdsaAlgo == PubKeyEcdsa256)
return 32;
if (ecdsaAlgo == PubKeyEcdsa384)
return 48;
if (ecdsaAlgo == PubKeyEcdsa521)
return 66;
throw SshClientException(SshInternalError, SSH_TR("Unexpected ecdsa algorithm \"%1\"")
.arg(QString::fromLatin1(ecdsaAlgo)));
}
QByteArray SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(int keyWidthInBytes)
{
if (keyWidthInBytes <= 32)
return PubKeyEcdsa256;
if (keyWidthInBytes <= 48)
return PubKeyEcdsa384;
if (keyWidthInBytes <= 66)
return PubKeyEcdsa521;
throw SshClientException(SshInternalError, SSH_TR("Unexpected ecdsa key size (%1 bytes)")
.arg(keyWidthInBytes));
}
const char *SshCapabilities::oid(const QByteArray &ecdsaAlgo)
{
if (ecdsaAlgo == PubKeyEcdsa256)
return "secp256r1";
if (ecdsaAlgo == PubKeyEcdsa384)
return "secp384r1";
if (ecdsaAlgo == PubKeyEcdsa521)
return "secp521r1";
throw SshClientException(SshInternalError, SSH_TR("Unexpected ecdsa algorithm \"%1\"")
.arg(QString::fromLatin1(ecdsaAlgo)));
}
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,83 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QByteArray>
#include <QList>
namespace QSsh {
namespace Internal {
class SshCapabilities
{
public:
static const QByteArray DiffieHellmanGroup1Sha1;
static const QByteArray DiffieHellmanGroup14Sha1;
static const QByteArray EcdhKexNamePrefix;
static const QByteArray EcdhNistp256;
static const QByteArray EcdhNistp384;
static const QByteArray EcdhNistp521; // sic
static const QList<QByteArray> KeyExchangeMethods;
static const QByteArray PubKeyDss;
static const QByteArray PubKeyRsa;
static const QByteArray PubKeyEcdsaPrefix;
static const QByteArray PubKeyEcdsa256;
static const QByteArray PubKeyEcdsa384;
static const QByteArray PubKeyEcdsa521;
static const QList<QByteArray> PublicKeyAlgorithms;
static const QByteArray CryptAlgo3DesCbc;
static const QByteArray CryptAlgo3DesCtr;
static const QByteArray CryptAlgoAes128Cbc;
static const QByteArray CryptAlgoAes128Ctr;
static const QByteArray CryptAlgoAes192Ctr;
static const QByteArray CryptAlgoAes256Ctr;
static const QList<QByteArray> EncryptionAlgorithms;
static const QByteArray HMacSha1;
static const QByteArray HMacSha196;
static const QByteArray HMacSha256;
static const QByteArray HMacSha384;
static const QByteArray HMacSha512;
static const QList<QByteArray> MacAlgorithms;
static const QList<QByteArray> CompressionAlgorithms;
static const QByteArray SshConnectionService;
static QList<QByteArray> commonCapabilities(const QList<QByteArray> &myCapabilities,
const QList<QByteArray> &serverCapabilities);
static QByteArray findBestMatch(const QList<QByteArray> &myCapabilities,
const QList<QByteArray> &serverCapabilities);
static int ecdsaIntegerWidthInBytes(const QByteArray &ecdsaAlgo);
static QByteArray ecdsaPubKeyAlgoForKeyWidth(int keyWidthInBytes);
static const char *oid(const QByteArray &ecdsaAlgo);
};
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,280 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshchannel_p.h"
#include "sshincomingpacket_p.h"
#include "sshlogging_p.h"
#include "sshsendfacility_p.h"
#include <botan/botan.h>
#include <QTimer>
namespace QSsh {
namespace Internal {
// "Payload length" (RFC 4253, 6.1), i.e. minus packet type, channel number
// and length field for string.
const quint32 MinMaxPacketSize = 32768 - sizeof(quint32) - sizeof(quint32) - 1;
const quint32 NoChannel = 0xffffffffu;
AbstractSshChannel::AbstractSshChannel(quint32 channelId,
SshSendFacility &sendFacility)
: m_sendFacility(sendFacility),
m_localChannel(channelId), m_remoteChannel(NoChannel),
m_localWindowSize(initialWindowSize()), m_remoteWindowSize(0),
m_state(Inactive)
{
m_timeoutTimer.setSingleShot(true);
connect(&m_timeoutTimer, &QTimer::timeout, this, &AbstractSshChannel::timeout);
}
AbstractSshChannel::~AbstractSshChannel()
{
}
void AbstractSshChannel::setChannelState(ChannelState state)
{
m_state = state;
if (state == Closed)
closeHook();
}
void AbstractSshChannel::requestSessionStart()
{
// Note: We are just being paranoid here about the Botan exceptions,
// which are extremely unlikely to happen, because if there was a problem
// with our cryptography stuff, it would have hit us before, on
// establishing the connection.
try {
m_sendFacility.sendSessionPacket(m_localChannel, initialWindowSize(), maxPacketSize());
setChannelState(SessionRequested);
m_timeoutTimer.start(ReplyTimeout);
} catch (const std::exception &e) {
qCWarning(sshLog, "Botan error: %s", e.what());
closeChannel();
}
}
void AbstractSshChannel::sendData(const QByteArray &data)
{
try {
m_sendBuffer += data;
flushSendBuffer();
} catch (const std::exception &e) {
qCWarning(sshLog, "Botan error: %s", e.what());
closeChannel();
}
}
quint32 AbstractSshChannel::initialWindowSize()
{
return maxPacketSize();
}
quint32 AbstractSshChannel::maxPacketSize()
{
return 16 * 1024 * 1024;
}
void AbstractSshChannel::handleWindowAdjust(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()
{
while (true) {
const quint32 bytesToSend = qMin(m_remoteMaxPacketSize,
qMin<quint32>(m_remoteWindowSize, m_sendBuffer.size()));
if (bytesToSend == 0)
break;
const QByteArray &data = m_sendBuffer.left(bytesToSend);
m_sendFacility.sendChannelDataPacket(m_remoteChannel, data);
m_sendBuffer.remove(0, bytesToSend);
m_remoteWindowSize -= bytesToSend;
}
}
void AbstractSshChannel::handleOpenSuccess(quint32 remoteChannelId,
quint32 remoteWindowSize, quint32 remoteMaxPacketSize)
{
const ChannelState oldState = m_state;
switch (oldState) {
case CloseRequested: // closeChannel() was called while we were in SessionRequested state
case SessionRequested:
break; // Ok, continue.
default:
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet.");
}
m_timeoutTimer.stop();
if (remoteMaxPacketSize < MinMaxPacketSize) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Maximum packet size too low.");
}
qCDebug(sshLog, "Channel opened. remote channel id: %u, remote window size: %u, "
"remote max packet size: %u",
remoteChannelId, remoteWindowSize, remoteMaxPacketSize);
m_remoteChannel = remoteChannelId;
m_remoteWindowSize = remoteWindowSize;
m_remoteMaxPacketSize = remoteMaxPacketSize;
setChannelState(SessionEstablished);
if (oldState == CloseRequested)
closeChannel();
else
handleOpenSuccessInternal();
}
void AbstractSshChannel::handleOpenFailure(const QString &reason)
{
switch (m_state) {
case SessionRequested:
break; // Ok, continue.
case CloseRequested:
return; // Late server reply; we requested a channel close in the meantime.
default:
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
}
m_timeoutTimer.stop();
qCDebug(sshLog, "Channel open request failed for channel %u", m_localChannel);
handleOpenFailureInternal(reason);
}
void AbstractSshChannel::handleChannelEof()
{
if (m_state == Inactive || m_state == Closed) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected SSH_MSG_CHANNEL_EOF message.");
}
m_localWindowSize = 0;
emit eof();
}
void AbstractSshChannel::handleChannelClose()
{
qCDebug(sshLog, "Receiving CLOSE for channel %u", m_localChannel);
if (channelState() == Inactive || channelState() == Closed) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected SSH_MSG_CHANNEL_CLOSE message.");
}
closeChannel();
setChannelState(Closed);
}
void AbstractSshChannel::handleChannelData(const QByteArray &data)
{
const int bytesToDeliver = handleChannelOrExtendedChannelData(data);
handleChannelDataInternal(bytesToDeliver == data.size()
? data : data.left(bytesToDeliver));
}
void AbstractSshChannel::handleChannelExtendedData(quint32 type, const QByteArray &data)
{
const int bytesToDeliver = handleChannelOrExtendedChannelData(data);
handleChannelExtendedDataInternal(type, bytesToDeliver == data.size()
? data : data.left(bytesToDeliver));
}
void AbstractSshChannel::handleChannelRequest(const SshIncomingPacket &packet)
{
checkChannelActive();
const QByteArray &requestType = packet.extractChannelRequestType();
if (requestType == SshIncomingPacket::ExitStatusType)
handleExitStatus(packet.extractChannelExitStatus());
else if (requestType == SshIncomingPacket::ExitSignalType)
handleExitSignal(packet.extractChannelExitSignal());
else if (requestType != "eow@openssh.com") // Suppress warning for this one, as it's sent all the time.
qCWarning(sshLog, "Ignoring unknown request type '%s'", requestType.data());
}
int AbstractSshChannel::handleChannelOrExtendedChannelData(const QByteArray &data)
{
checkChannelActive();
const int bytesToDeliver = qMin<quint32>(data.size(), maxDataSize());
if (bytesToDeliver != data.size())
qCWarning(sshLog, "Misbehaving server does not respect local window, clipping.");
m_localWindowSize -= bytesToDeliver;
if (m_localWindowSize < maxPacketSize()) {
m_localWindowSize += maxPacketSize();
m_sendFacility.sendWindowAdjustPacket(m_remoteChannel, maxPacketSize());
}
return bytesToDeliver;
}
void AbstractSshChannel::closeChannel()
{
if (m_state == CloseRequested) {
m_timeoutTimer.stop();
} else if (m_state != Closed) {
if (m_state == Inactive) {
setChannelState(Closed);
} else {
const ChannelState oldState = m_state;
setChannelState(CloseRequested);
if (m_remoteChannel != NoChannel) {
m_sendFacility.sendChannelEofPacket(m_remoteChannel);
m_sendFacility.sendChannelClosePacket(m_remoteChannel);
} else {
QSSH_ASSERT(oldState == SessionRequested);
}
}
}
}
void AbstractSshChannel::checkChannelActive()
{
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

View file

@ -0,0 +1,117 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QByteArray>
#include <QObject>
#include <QString>
#include <QTimer>
namespace QSsh {
namespace Internal {
struct SshChannelExitSignal;
struct SshChannelExitStatus;
class SshIncomingPacket;
class SshSendFacility;
class AbstractSshChannel : public QObject
{
Q_OBJECT
public:
enum ChannelState {
Inactive, SessionRequested, SessionEstablished, CloseRequested, Closed
};
quint32 localChannelId() const { return m_localChannel; }
quint32 remoteChannel() const { return m_remoteChannel; }
virtual void handleChannelSuccess() = 0;
virtual void handleChannelFailure() = 0;
void handleOpenSuccess(quint32 remoteChannelId, quint32 remoteWindowSize,
quint32 remoteMaxPacketSize);
void handleOpenFailure(const QString &reason);
void handleWindowAdjust(quint32 bytesToAdd);
void handleChannelEof();
void handleChannelClose();
void handleChannelData(const QByteArray &data);
void handleChannelExtendedData(quint32 type, const QByteArray &data);
void handleChannelRequest(const SshIncomingPacket &packet);
void closeChannel();
virtual ~AbstractSshChannel();
static const int ReplyTimeout = 10000; // milli seconds
ChannelState channelState() const { return m_state; }
signals:
void timeout();
void eof();
protected:
AbstractSshChannel(quint32 channelId, SshSendFacility &sendFacility);
void setChannelState(ChannelState state);
void requestSessionStart();
void sendData(const QByteArray &data);
static quint32 initialWindowSize();
static quint32 maxPacketSize();
quint32 maxDataSize() const;
void checkChannelActive();
SshSendFacility &m_sendFacility;
QTimer m_timeoutTimer;
private:
virtual void handleOpenSuccessInternal() = 0;
virtual void handleOpenFailureInternal(const QString &reason) = 0;
virtual void handleChannelDataInternal(const QByteArray &data) = 0;
virtual void handleChannelExtendedDataInternal(quint32 type,
const QByteArray &data) = 0;
virtual void handleExitStatus(const SshChannelExitStatus &exitStatus) = 0;
virtual void handleExitSignal(const SshChannelExitSignal &signal) = 0;
virtual void closeHook() = 0;
void flushSendBuffer();
int handleChannelOrExtendedChannelData(const QByteArray &data);
const quint32 m_localChannel;
quint32 m_remoteChannel;
quint32 m_localWindowSize;
quint32 m_remoteWindowSize;
quint32 m_remoteMaxPacketSize;
ChannelState m_state;
QByteArray m_sendBuffer;
};
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,328 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshchannelmanager_p.h"
#include "sftpchannel.h"
#include "sftpchannel_p.h"
#include "sshdirecttcpiptunnel.h"
#include "sshdirecttcpiptunnel_p.h"
#include "sshforwardedtcpiptunnel.h"
#include "sshforwardedtcpiptunnel_p.h"
#include "sshincomingpacket_p.h"
#include "sshlogging_p.h"
#include "sshremoteprocess.h"
#include "sshremoteprocess_p.h"
#include "sshsendfacility_p.h"
#include "sshtcpipforwardserver.h"
#include "sshtcpipforwardserver_p.h"
#include <QList>
namespace QSsh {
namespace Internal {
SshChannelManager::SshChannelManager(SshSendFacility &sendFacility,
QObject *parent)
: QObject(parent), m_sendFacility(sendFacility), m_nextLocalChannelId(0)
{
}
void SshChannelManager::handleChannelRequest(const SshIncomingPacket &packet)
{
lookupChannel(packet.extractRecipientChannel())
->handleChannelRequest(packet);
}
void SshChannelManager::handleChannelOpen(const SshIncomingPacket &packet)
{
SshChannelOpen channelOpen = packet.extractChannelOpen();
SshTcpIpForwardServer::Ptr server;
foreach (const SshTcpIpForwardServer::Ptr &candidate, m_listeningForwardServers) {
if (candidate->port() == channelOpen.remotePort
&& candidate->bindAddress().toUtf8() == channelOpen.remoteAddress) {
server = candidate;
break;
}
};
if (server.isNull()) {
// Apparently the server knows a remoteAddress we are not aware of. There are plenty of ways
// to make that happen: /etc/hosts on the server, different writings for localhost,
// different DNS servers, ...
// Rather than trying to figure that out, we just use the first listening forwarder with the
// same port.
foreach (const SshTcpIpForwardServer::Ptr &candidate, m_listeningForwardServers) {
if (candidate->port() == channelOpen.remotePort) {
server = candidate;
break;
}
};
}
if (server.isNull()) {
SshOpenFailureType reason = (channelOpen.remotePort == 0) ?
SSH_OPEN_UNKNOWN_CHANNEL_TYPE : SSH_OPEN_ADMINISTRATIVELY_PROHIBITED;
try {
m_sendFacility.sendChannelOpenFailurePacket(channelOpen.remoteChannel, reason,
QByteArray());
} catch (const std::exception &e) {
qCWarning(sshLog, "Botan error: %s", e.what());
}
return;
}
SshForwardedTcpIpTunnel::Ptr tunnel(new SshForwardedTcpIpTunnel(m_nextLocalChannelId++,
m_sendFacility));
tunnel->d->handleOpenSuccess(channelOpen.remoteChannel, channelOpen.remoteWindowSize,
channelOpen.remoteMaxPacketSize);
tunnel->open(QIODevice::ReadWrite);
server->setNewConnection(tunnel);
insertChannel(tunnel->d, tunnel);
}
void SshChannelManager::handleChannelOpenFailure(const SshIncomingPacket &packet)
{
const SshChannelOpenFailure &failure = packet.extractChannelOpenFailure();
ChannelIterator it = lookupChannelAsIterator(failure.localChannel);
try {
it.value()->handleOpenFailure(failure.reasonString);
} catch (const SshServerException &e) {
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);
}
}
void SshChannelManager::handleRequestSuccess(const SshIncomingPacket &packet)
{
if (m_waitingForwardServers.isEmpty()) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected request success packet.",
tr("Unexpected request success packet."));
}
SshTcpIpForwardServer::Ptr server = m_waitingForwardServers.takeFirst();
if (server->state() == SshTcpIpForwardServer::Closing) {
server->setClosed();
} else if (server->state() == SshTcpIpForwardServer::Initializing) {
quint16 port = server->port();
if (port == 0)
port = packet.extractRequestSuccess().bindPort;
server->setListening(port);
m_listeningForwardServers.append(server);
} else {
QSSH_ASSERT(false);
}
}
void SshChannelManager::handleRequestFailure(const SshIncomingPacket &packet)
{
Q_UNUSED(packet);
if (m_waitingForwardServers.isEmpty()) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected request failure packet.",
tr("Unexpected request failure packet."));
}
SshTcpIpForwardServer::Ptr tunnel = m_waitingForwardServers.takeFirst();
tunnel->setClosed();
}
SshChannelManager::ChannelIterator SshChannelManager::lookupChannelAsIterator(quint32 channelId,
bool allowNotFound)
{
ChannelIterator it = m_channels.find(channelId);
if (it == m_channels.end() && !allowNotFound) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Invalid channel id.",
tr("Invalid channel id %1").arg(channelId));
}
return it;
}
AbstractSshChannel *SshChannelManager::lookupChannel(quint32 channelId,
bool allowNotFound)
{
ChannelIterator it = lookupChannelAsIterator(channelId, allowNotFound);
return it == m_channels.end() ? 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;
}
SshDirectTcpIpTunnel::Ptr SshChannelManager::createDirectTunnel(const QString &originatingHost,
quint16 originatingPort, const QString &remoteHost, quint16 remotePort)
{
SshDirectTcpIpTunnel::Ptr tunnel(new SshDirectTcpIpTunnel(m_nextLocalChannelId++,
originatingHost, originatingPort, remoteHost, remotePort, m_sendFacility));
insertChannel(tunnel->d, tunnel);
return tunnel;
}
SshTcpIpForwardServer::Ptr SshChannelManager::createForwardServer(const QString &remoteHost,
quint16 remotePort)
{
SshTcpIpForwardServer::Ptr server(new SshTcpIpForwardServer(remoteHost, remotePort,
m_sendFacility));
connect(server.data(), &SshTcpIpForwardServer::stateChanged,
this, [this, server](SshTcpIpForwardServer::State state) {
switch (state) {
case SshTcpIpForwardServer::Closing:
m_listeningForwardServers.removeOne(server);
// fall through
case SshTcpIpForwardServer::Initializing:
m_waitingForwardServers.append(server);
break;
case SshTcpIpForwardServer::Listening:
case SshTcpIpForwardServer::Inactive:
break;
}
});
return server;
}
void SshChannelManager::insertChannel(AbstractSshChannel *priv,
const QSharedPointer<QObject> &pub)
{
connect(priv, &AbstractSshChannel::timeout, this, &SshChannelManager::timeout);
m_channels.insert(priv->localChannelId(), priv);
m_sessions.insert(priv, pub);
}
int SshChannelManager::closeAllChannels(CloseAllMode mode)
{
int count = 0;
for (ChannelIterator it = m_channels.begin(); it != m_channels.end(); ++it) {
AbstractSshChannel * const channel = it.value();
QSSH_ASSERT(channel->channelState() != AbstractSshChannel::Closed);
if (channel->channelState() != AbstractSshChannel::CloseRequested) {
channel->closeChannel();
++count;
}
}
if (mode == CloseAllAndReset) {
m_channels.clear();
m_sessions.clear();
}
return count;
}
int SshChannelManager::channelCount() const
{
return m_channels.count();
}
void SshChannelManager::removeChannel(ChannelIterator it)
{
if (it == m_channels.end()) {
throw SshClientException(SshInternalError,
QLatin1String("Internal error: Unexpected channel lookup failure"));
}
const int removeCount = m_sessions.remove(it.value());
if (removeCount != 1) {
throw SshClientException(SshInternalError,
QString::fromLatin1("Internal error: Unexpected session count %1 for channel.")
.arg(removeCount));
}
m_channels.erase(it);
}
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,99 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QHash>
#include <QObject>
#include <QSharedPointer>
namespace QSsh {
class SftpChannel;
class SshDirectTcpIpTunnel;
class SshRemoteProcess;
class SshTcpIpForwardServer;
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();
QSharedPointer<SshDirectTcpIpTunnel> createDirectTunnel(const QString &originatingHost,
quint16 originatingPort, const QString &remoteHost, quint16 remotePort);
QSharedPointer<SshTcpIpForwardServer> createForwardServer(const QString &remoteHost,
quint16 remotePort);
int channelCount() const;
enum CloseAllMode { CloseAllRegular, CloseAllAndReset };
int closeAllChannels(CloseAllMode mode);
void handleChannelRequest(const SshIncomingPacket &packet);
void handleChannelOpen(const SshIncomingPacket &packet);
void handleChannelOpenFailure(const SshIncomingPacket &packet);
void handleChannelOpenConfirmation(const SshIncomingPacket &packet);
void handleChannelSuccess(const SshIncomingPacket &packet);
void handleChannelFailure(const SshIncomingPacket &packet);
void handleChannelWindowAdjust(const SshIncomingPacket &packet);
void handleChannelData(const SshIncomingPacket &packet);
void handleChannelExtendedData(const SshIncomingPacket &packet);
void handleChannelEof(const SshIncomingPacket &packet);
void handleChannelClose(const SshIncomingPacket &packet);
void handleRequestSuccess(const SshIncomingPacket &packet);
void handleRequestFailure(const SshIncomingPacket &packet);
signals:
void timeout();
private:
typedef QHash<quint32, AbstractSshChannel *>::Iterator ChannelIterator;
ChannelIterator lookupChannelAsIterator(quint32 channelId,
bool allowNotFound = false);
AbstractSshChannel *lookupChannel(quint32 channelId,
bool allowNotFound = false);
void removeChannel(ChannelIterator it);
void insertChannel(AbstractSshChannel *priv,
const QSharedPointer<QObject> &pub);
SshSendFacility &m_sendFacility;
QHash<quint32, AbstractSshChannel *> m_channels;
QHash<AbstractSshChannel *, QSharedPointer<QObject> > m_sessions;
quint32 m_nextLocalChannelId;
QList<QSharedPointer<SshTcpIpForwardServer>> m_waitingForwardServers;
QList<QSharedPointer<SshTcpIpForwardServer>> m_listeningForwardServers;
};
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,864 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshconnection.h"
#include "sshconnection_p.h"
#include "sftpchannel.h"
#include "sshcapabilities_p.h"
#include "sshchannelmanager_p.h"
#include "sshcryptofacility_p.h"
#include "sshdirecttcpiptunnel.h"
#include "sshtcpipforwardserver.h"
#include "sshexception_p.h"
#include "sshinit_p.h"
#include "sshkeyexchange_p.h"
#include "sshlogging_p.h"
#include "sshremoteprocess.h"
#include <botan/botan.h>
#include <QFile>
#include <QMutex>
#include <QMutexLocker>
#include <QNetworkProxy>
#include <QRegExp>
#include <QTcpSocket>
/*!
\class QSsh::SshConnection
\brief The SshConnection 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 {
const QByteArray ClientId("SSH-2.0-QtCreator\r\n");
SshConnectionParameters::SshConnectionParameters() :
timeout(0), authenticationType(AuthenticationTypePublicKey), port(0),
hostKeyCheckingMode(SshHostKeyCheckingNone)
{
options |= SshIgnoreDefaultProxy;
options |= SshEnableStrictConformanceChecks;
}
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::AuthenticationTypePassword ?
p1.password == p2.password : p1.privateKeyFile == p2.privateKeyFile)
&& p1.hostKeyCheckingMode == p2.hostKeyCheckingMode
&& 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);
}
SshConnection::SshConnection(const SshConnectionParameters &serverInfo, QObject *parent)
: QObject(parent)
{
Internal::initSsh();
qRegisterMetaType<QSsh::SshError>("QSsh::SshError");
qRegisterMetaType<QSsh::SftpJobId>("QSsh::SftpJobId");
qRegisterMetaType<QSsh::SftpFileInfo>("QSsh::SftpFileInfo");
qRegisterMetaType<QList <QSsh::SftpFileInfo> >("QList<QSsh::SftpFileInfo>");
d = new Internal::SshConnectionPrivate(this, serverInfo);
connect(d, &Internal::SshConnectionPrivate::connected, this, &SshConnection::connected,
Qt::QueuedConnection);
connect(d, &Internal::SshConnectionPrivate::dataAvailable, this,
&SshConnection::dataAvailable, Qt::QueuedConnection);
connect(d, &Internal::SshConnectionPrivate::disconnected, this, &SshConnection::disconnected,
Qt::QueuedConnection);
connect(d, &Internal::SshConnectionPrivate::error, this,
&SshConnection::error, Qt::QueuedConnection);
}
void SshConnection::connectToHost()
{
d->connectToHost();
}
void SshConnection::disconnectFromHost()
{
d->closeConnection(Internal::SSH_DISCONNECT_BY_APPLICATION, SshNoError, "",
QString());
}
SshConnection::State SshConnection::state() const
{
switch (d->state()) {
case Internal::SocketUnconnected:
return Unconnected;
case Internal::ConnectionEstablished:
return Connected;
default:
return Connecting;
}
}
SshError SshConnection::errorState() const
{
return d->errorState();
}
QString SshConnection::errorString() const
{
return d->errorString();
}
SshConnectionParameters SshConnection::connectionParameters() const
{
return d->m_connParams;
}
SshConnectionInfo SshConnection::connectionInfo() const
{
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshConnectionInfo());
return SshConnectionInfo(d->m_socket->localAddress(), d->m_socket->localPort(),
d->m_socket->peerAddress(), d->m_socket->peerPort());
}
SshConnection::~SshConnection()
{
disconnect();
disconnectFromHost();
delete d;
}
QSharedPointer<SshRemoteProcess> SshConnection::createRemoteProcess(const QByteArray &command)
{
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SshRemoteProcess>());
return d->createRemoteProcess(command);
}
QSharedPointer<SshRemoteProcess> SshConnection::createRemoteShell()
{
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SshRemoteProcess>());
return d->createRemoteShell();
}
QSharedPointer<SftpChannel> SshConnection::createSftpChannel()
{
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SftpChannel>());
return d->createSftpChannel();
}
SshDirectTcpIpTunnel::Ptr SshConnection::createDirectTunnel(const QString &originatingHost,
quint16 originatingPort, const QString &remoteHost, quint16 remotePort)
{
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshDirectTcpIpTunnel::Ptr());
return d->createDirectTunnel(originatingHost, originatingPort, remoteHost, remotePort);
}
QSharedPointer<SshTcpIpForwardServer> SshConnection::createForwardServer(const QString &remoteHost,
quint16 remotePort)
{
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshTcpIpForwardServer::Ptr());
return d->createForwardServer(remoteHost, remotePort);
}
int SshConnection::closeAllChannels()
{
try {
return d->m_channelManager->closeAllChannels(Internal::SshChannelManager::CloseAllRegular);
} catch (const std::exception &e) {
qCWarning(Internal::sshLog, "%s: %s", Q_FUNC_INFO, e.what());
return -1;
}
}
int SshConnection::channelCount() const
{
return d->m_channelManager->channelCount();
}
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.options & SshIgnoreDefaultProxy)
? QNetworkProxy::NoProxy : QNetworkProxy::DefaultProxy);
m_timeoutTimer.setSingleShot(true);
m_timeoutTimer.setInterval(m_connParams.timeout * 1000);
m_keepAliveTimer.setSingleShot(true);
m_keepAliveTimer.setInterval(10000);
connect(m_channelManager, &SshChannelManager::timeout,
this, &SshConnectionPrivate::handleTimeout);
}
SshConnectionPrivate::~SshConnectionPrivate()
{
disconnect();
}
void SshConnectionPrivate::setupPacketHandlers()
{
typedef SshConnectionPrivate This;
setupPacketHandler(SSH_MSG_KEXINIT, StateList() << SocketConnected
<< ConnectionEstablished, &This::handleKeyExchangeInitPacket);
setupPacketHandler(SSH_MSG_KEXDH_REPLY, StateList() << SocketConnected
<< ConnectionEstablished, &This::handleKeyExchangeReplyPacket);
setupPacketHandler(SSH_MSG_NEWKEYS, StateList() << SocketConnected
<< ConnectionEstablished, &This::handleNewKeysPacket);
setupPacketHandler(SSH_MSG_SERVICE_ACCEPT,
StateList() << UserAuthServiceRequested,
&This::handleServiceAcceptPacket);
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePassword
|| m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods) {
setupPacketHandler(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
StateList() << UserAuthRequested, &This::handlePasswordExpiredPacket);
}
setupPacketHandler(SSH_MSG_GLOBAL_REQUEST,
StateList() << ConnectionEstablished, &This::handleGlobalRequest);
const StateList authReqList = StateList() << UserAuthRequested;
setupPacketHandler(SSH_MSG_USERAUTH_BANNER, authReqList,
&This::handleUserAuthBannerPacket);
setupPacketHandler(SSH_MSG_USERAUTH_SUCCESS, authReqList,
&This::handleUserAuthSuccessPacket);
setupPacketHandler(SSH_MSG_USERAUTH_FAILURE, authReqList,
&This::handleUserAuthFailurePacket);
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeKeyboardInteractive
|| m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods) {
setupPacketHandler(SSH_MSG_USERAUTH_INFO_REQUEST, authReqList,
&This::handleUserAuthInfoRequestPacket);
}
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);
setupPacketHandler(SSH_MSG_REQUEST_SUCCESS, connectedList,
&This::handleRequestSuccess);
setupPacketHandler(SSH_MSG_REQUEST_FAILURE, connectedList,
&This::handleRequestFailure);
}
void SshConnectionPrivate::setupPacketHandler(SshPacketType type,
const SshConnectionPrivate::StateList &states,
SshConnectionPrivate::PacketHandler handler)
{
m_packetHandlers.insert(type, HandlerInStates(states, handler));
}
void SshConnectionPrivate::handleSocketConnected()
{
m_state = SocketConnected;
sendData(ClientId);
}
void SshConnectionPrivate::handleIncomingData()
{
if (m_state == SocketUnconnected)
return; // For stuff queued in the event loop after we've called closeConnection();
try {
if (!canUseSocket())
return;
m_incomingData += m_socket->readAll();
qCDebug(sshLog, "state = %d, remote data size = %d", m_state, m_incomingData.count());
if (m_serverId.isEmpty())
handleServerId();
handlePackets();
} catch (const SshServerException &e) {
closeConnection(e.error, SshProtocolError, e.errorStringServer,
tr("SSH Protocol error: %1").arg(e.errorStringUser));
} catch (const SshClientException &e) {
closeConnection(SSH_DISCONNECT_BY_APPLICATION, e.error, "",
e.errorString);
} catch (const std::exception &e) {
closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshInternalError, "",
tr("Botan library exception: %1").arg(QString::fromLatin1(e.what())));
}
}
// RFC 4253, 4.2.
void SshConnectionPrivate::handleServerId()
{
qCDebug(sshLog, "%s: incoming data size = %d, incoming data = '%s'",
Q_FUNC_INFO, m_incomingData.count(), m_incomingData.data());
const int newLinePos = m_incomingData.indexOf('\n');
if (newLinePos == -1)
return; // Not enough data yet.
// Lines not starting with "SSH-" are ignored.
if (!m_incomingData.startsWith("SSH-")) {
m_incomingData.remove(0, newLinePos + 1);
m_serverHasSentDataBeforeId = true;
return;
}
if (newLinePos > 255 - 1) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Identification string too long.",
tr("Server identification string is %n characters long, but the maximum "
"allowed length is 255.", 0, newLinePos + 1));
}
const bool hasCarriageReturn = m_incomingData.at(newLinePos - 1) == '\r';
m_serverId = m_incomingData.left(newLinePos);
if (hasCarriageReturn)
m_serverId.chop(1);
m_incomingData.remove(0, newLinePos + 1);
if (m_serverId.contains('\0')) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Identification string contains illegal NUL character.",
tr("Server identification string contains illegal NUL character."));
}
// "printable US-ASCII characters, with the exception of whitespace characters
// and the minus sign"
QString legalString = QLatin1String("[]!\"#$!&'()*+,./0-9:;<=>?@A-Z[\\\\^_`a-z{|}~]+");
const QRegExp versionIdpattern(QString::fromLatin1("SSH-(%1)-%1(?: .+)?").arg(legalString));
if (!versionIdpattern.exactMatch(QString::fromLatin1(m_serverId))) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Identification string is invalid.",
tr("Server Identification string \"%1\" is invalid.")
.arg(QString::fromLatin1(m_serverId)));
}
const QString serverProtoVersion = versionIdpattern.cap(1);
if (serverProtoVersion != QLatin1String("2.0") && serverProtoVersion != QLatin1String("1.99")) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
"Invalid protocol version.",
tr("Server protocol version is \"%1\", but needs to be 2.0 or 1.99.")
.arg(serverProtoVersion));
}
if (m_connParams.options & SshEnableStrictConformanceChecks) {
if (serverProtoVersion == QLatin1String("2.0") && !hasCarriageReturn) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Identification string is invalid.",
tr("Server identification string is invalid (missing carriage return)."));
}
if (serverProtoVersion == QLatin1String("1.99") && m_serverHasSentDataBeforeId) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"No extra data preceding identification string allowed for 1.99.",
tr("Server reports protocol version 1.99, but sends data "
"before the identification string, which is not allowed."));
}
}
m_keyExchange.reset(new SshKeyExchange(m_connParams, m_sendFacility));
m_keyExchange->sendKexInitPacket(m_serverId);
m_keyExchangeState = KexInitSent;
}
void SshConnectionPrivate::handlePackets()
{
m_incomingPacket.consumeData(m_incomingData);
while (m_incomingPacket.isComplete()) {
handleCurrentPacket();
m_incomingPacket.clear();
m_incomingPacket.consumeData(m_incomingData);
}
}
void SshConnectionPrivate::handleCurrentPacket()
{
Q_ASSERT(m_incomingPacket.isComplete());
Q_ASSERT(m_keyExchangeState == DhInitSent || !m_ignoreNextPacket);
if (m_ignoreNextPacket) {
m_ignoreNextPacket = false;
return;
}
QHash<SshPacketType, HandlerInStates>::ConstIterator it
= m_packetHandlers.constFind(m_incomingPacket.type());
if (it == m_packetHandlers.constEnd()) {
m_sendFacility.sendMsgUnimplementedPacket(m_incomingPacket.serverSeqNr());
return;
}
if (!it.value().first.contains(m_state)) {
handleUnexpectedPacket();
return;
}
(this->*it.value().second)();
}
void SshConnectionPrivate::handleKeyExchangeInitPacket()
{
if (m_keyExchangeState != NoKeyExchange
&& m_keyExchangeState != KexInitSent) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected packet.", tr("Unexpected packet of type %1.")
.arg(m_incomingPacket.type()));
}
// Server-initiated re-exchange.
if (m_keyExchangeState == NoKeyExchange) {
m_keyExchange.reset(new SshKeyExchange(m_connParams, m_sendFacility));
m_keyExchange->sendKexInitPacket(m_serverId);
}
// If the server sends a guessed packet, the guess must be wrong,
// because the algorithms we support require us to initiate the
// key exchange.
if (m_keyExchange->sendDhInitPacket(m_incomingPacket))
m_ignoreNextPacket = true;
m_keyExchangeState = DhInitSent;
}
void SshConnectionPrivate::handleKeyExchangeReplyPacket()
{
if (m_keyExchangeState != DhInitSent) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected packet.", tr("Unexpected packet of type %1.")
.arg(m_incomingPacket.type()));
}
m_keyExchange->sendNewKeysPacket(m_incomingPacket,
ClientId.left(ClientId.size() - 2));
m_sendFacility.recreateKeys(*m_keyExchange);
m_keyExchangeState = NewKeysSent;
}
void SshConnectionPrivate::handleNewKeysPacket()
{
if (m_keyExchangeState != NewKeysSent) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected packet.", tr("Unexpected packet of type %1.")
.arg(m_incomingPacket.type()));
}
m_incomingPacket.recreateKeys(*m_keyExchange);
m_keyExchange.reset();
m_keyExchangeState = NoKeyExchange;
if (m_state == SocketConnected) {
m_sendFacility.sendUserAuthServiceRequestPacket();
m_state = UserAuthServiceRequested;
}
}
void SshConnectionPrivate::handleServiceAcceptPacket()
{
switch (m_connParams.authenticationType) {
case SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods:
m_triedAllPasswordBasedMethods = false;
// Fall-through.
case SshConnectionParameters::AuthenticationTypePassword:
m_sendFacility.sendUserAuthByPasswordRequestPacket(m_connParams.userName.toUtf8(),
SshCapabilities::SshConnectionService, m_connParams.password.toUtf8());
break;
case SshConnectionParameters::AuthenticationTypeKeyboardInteractive:
m_sendFacility.sendUserAuthByKeyboardInteractiveRequestPacket(m_connParams.userName.toUtf8(),
SshCapabilities::SshConnectionService);
break;
case SshConnectionParameters::AuthenticationTypePublicKey:
m_sendFacility.sendUserAuthByPublicKeyRequestPacket(m_connParams.userName.toUtf8(),
SshCapabilities::SshConnectionService);
break;
}
m_state = UserAuthRequested;
}
void SshConnectionPrivate::handlePasswordExpiredPacket()
{
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
&& m_triedAllPasswordBasedMethods) {
// This means we just tried to authorize via "keyboard-interactive", in which case
// this type of packet is not allowed.
handleUnexpectedPacket();
return;
}
throw SshClientException(SshAuthenticationError, tr("Password expired."));
}
void SshConnectionPrivate::handleUserAuthInfoRequestPacket()
{
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
&& !m_triedAllPasswordBasedMethods) {
// This means we just tried to authorize via "password", in which case
// this type of packet is not allowed.
handleUnexpectedPacket();
return;
}
const SshUserAuthInfoRequestPacket requestPacket
= m_incomingPacket.extractUserAuthInfoRequest();
QStringList responses;
responses.reserve(requestPacket.prompts.count());
// Not very interactive, admittedly, but we don't want to be for now.
for (int i = 0; i < requestPacket.prompts.count(); ++i)
responses << m_connParams.password;
m_sendFacility.sendUserAuthInfoResponsePacket(responses);
}
void SshConnectionPrivate::handleUserAuthBannerPacket()
{
emit dataAvailable(m_incomingPacket.extractUserAuthBanner().message);
}
void SshConnectionPrivate::handleUnexpectedPacket()
{
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected packet.", tr("Unexpected packet of type %1.")
.arg(m_incomingPacket.type()));
}
void SshConnectionPrivate::handleGlobalRequest()
{
m_sendFacility.sendRequestFailurePacket();
}
void SshConnectionPrivate::handleUserAuthSuccessPacket()
{
m_state = ConnectionEstablished;
m_timeoutTimer.stop();
emit connected();
m_lastInvalidMsgSeqNr = InvalidSeqNr;
connect(&m_keepAliveTimer, &QTimer::timeout, this, &SshConnectionPrivate::sendKeepAlivePacket);
m_keepAliveTimer.start();
}
void SshConnectionPrivate::handleUserAuthFailurePacket()
{
// TODO: Evaluate "authentications that can continue" field and act on it.
if (m_connParams.authenticationType
== SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
&& !m_triedAllPasswordBasedMethods) {
m_triedAllPasswordBasedMethods = true;
m_sendFacility.sendUserAuthByKeyboardInteractiveRequestPacket(
m_connParams.userName.toUtf8(),
SshCapabilities::SshConnectionService);
return;
}
m_timeoutTimer.stop();
const QString errorMsg = m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey
? tr("Server rejected key.") : tr("Server rejected password.");
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::handleRequestSuccess()
{
m_channelManager->handleRequestSuccess(m_incomingPacket);
}
void SshConnectionPrivate::handleRequestFailure()
{
m_channelManager->handleRequestFailure(m_incomingPacket);
}
void SshConnectionPrivate::sendData(const QByteArray &data)
{
if (canUseSocket())
m_socket->write(data);
}
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();
m_serverId.clear();
m_serverHasSentDataBeforeId = false;
try {
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey)
createPrivateKey();
} catch (const SshClientException &ex) {
m_error = ex.error;
m_errorString = ex.errorString;
emit error(m_error);
return;
}
connect(m_socket, &QAbstractSocket::connected,
this, &SshConnectionPrivate::handleSocketConnected);
connect(m_socket, &QIODevice::readyRead,
this, &SshConnectionPrivate::handleIncomingData);
connect(m_socket,
static_cast<void (QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
this, &SshConnectionPrivate::handleSocketError);
connect(m_socket, &QAbstractSocket::disconnected,
this, &SshConnectionPrivate::handleSocketDisconnected);
connect(&m_timeoutTimer, &QTimer::timeout, this, &SshConnectionPrivate::handleTimeout);
m_state = SocketConnecting;
m_keyExchangeState = NoKeyExchange;
m_timeoutTimer.start();
m_socket->connectToHost(m_connParams.host, m_connParams.port);
}
void SshConnectionPrivate::closeConnection(SshErrorCode sshError,
SshError userError, const QByteArray &serverErrorString,
const QString &userErrorString)
{
// Prevent endless loops by recursive exceptions.
if (m_state == SocketUnconnected || m_error != SshNoError)
return;
m_error = userError;
m_errorString = userErrorString;
m_timeoutTimer.stop();
disconnect(m_socket, 0, this, 0);
disconnect(&m_timeoutTimer, 0, this, 0);
m_keepAliveTimer.stop();
disconnect(&m_keepAliveTimer, 0, this, 0);
try {
m_channelManager->closeAllChannels(SshChannelManager::CloseAllAndReset);
m_sendFacility.sendDisconnectPacket(sshError, serverErrorString);
} catch (...) {} // Nothing sensible to be done here.
if (m_error != SshNoError)
emit error(userError);
if (m_state == ConnectionEstablished)
emit disconnected();
if (canUseSocket())
m_socket->disconnectFromHost();
m_state = SocketUnconnected;
}
bool SshConnectionPrivate::canUseSocket() const
{
return m_socket->isValid()
&& m_socket->state() == QAbstractSocket::ConnectedState;
}
void SshConnectionPrivate::createPrivateKey()
{
if (m_connParams.privateKeyFile.isEmpty())
throw SshClientException(SshKeyFileError, tr("No private key file given."));
QFile keyFile(m_connParams.privateKeyFile);
if (!keyFile.open(QIODevice::ReadOnly)) {
throw SshClientException(SshKeyFileError,
tr("Private key file error: %1").arg(keyFile.errorString()));
}
m_sendFacility.createAuthenticationKey(keyFile.readAll());
}
QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)
{
return m_channelManager->createRemoteProcess(command);
}
QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteShell()
{
return m_channelManager->createRemoteShell();
}
QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
{
return m_channelManager->createSftpChannel();
}
SshDirectTcpIpTunnel::Ptr SshConnectionPrivate::createDirectTunnel(const QString &originatingHost,
quint16 originatingPort, const QString &remoteHost, quint16 remotePort)
{
return m_channelManager->createDirectTunnel(originatingHost, originatingPort, remoteHost,
remotePort);
}
SshTcpIpForwardServer::Ptr SshConnectionPrivate::createForwardServer(const QString &bindAddress,
quint16 bindPort)
{
return m_channelManager->createForwardServer(bindAddress, bindPort);
}
const quint64 SshConnectionPrivate::InvalidSeqNr = static_cast<quint64>(-1);
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,145 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "ssherrors.h"
#include "sshhostkeydatabase.h"
#include "ssh_global.h"
#include <QByteArray>
#include <QFlags>
#include <QObject>
#include <QSharedPointer>
#include <QString>
#include <QHostAddress>
namespace QSsh {
class SftpChannel;
class SshDirectTcpIpTunnel;
class SshRemoteProcess;
class SshTcpIpForwardServer;
namespace Internal { class SshConnectionPrivate; }
enum SshConnectionOption {
SshIgnoreDefaultProxy = 0x1,
SshEnableStrictConformanceChecks = 0x2
};
Q_DECLARE_FLAGS(SshConnectionOptions, SshConnectionOption)
enum SshHostKeyCheckingMode {
SshHostKeyCheckingNone,
SshHostKeyCheckingStrict,
SshHostKeyCheckingAllowNoMatch,
SshHostKeyCheckingAllowMismatch
};
class QSSH_EXPORT SshConnectionParameters
{
public:
enum AuthenticationType {
AuthenticationTypePassword,
AuthenticationTypePublicKey,
AuthenticationTypeKeyboardInteractive,
// Some servers disable "password", others disable "keyboard-interactive".
AuthenticationTypeTryAllPasswordBasedMethods
};
SshConnectionParameters();
QString host;
QString userName;
QString password;
QString privateKeyFile;
int timeout; // In seconds.
AuthenticationType authenticationType;
quint16 port;
SshConnectionOptions options;
SshHostKeyCheckingMode hostKeyCheckingMode;
SshHostKeyDatabasePtr hostKeyDatabase;
};
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 };
explicit SshConnection(const SshConnectionParameters &serverInfo, QObject *parent = 0);
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();
QSharedPointer<SshDirectTcpIpTunnel> createDirectTunnel(const QString &originatingHost,
quint16 originatingPort, const QString &remoteHost, quint16 remotePort);
QSharedPointer<SshTcpIpForwardServer> createForwardServer(const QString &remoteHost,
quint16 remotePort);
// -1 if an error occurred, number of channels closed otherwise.
int closeAllChannels();
int channelCount() const;
signals:
void connected();
void disconnected();
void dataAvailable(const QString &message);
void error(QSsh::SshError);
private:
Internal::SshConnectionPrivate *d;
};
} // namespace QSsh

View file

@ -0,0 +1,179 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "sshconnection.h"
#include "sshexception_p.h"
#include "sshincomingpacket_p.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 QSsh {
class SftpChannel;
class SshRemoteProcess;
class SshDirectTcpIpTunnel;
class SshTcpIpForwardServer;
namespace Internal {
class SshChannelManager;
// NOTE: When you add stuff here, don't forget to update m_packetHandlers.
enum SshStateInternal {
SocketUnconnected, // initial and after disconnect
SocketConnecting, // After connectToHost()
SocketConnected, // After socket's connected() signal
UserAuthServiceRequested,
UserAuthRequested,
ConnectionEstablished // After service has been started
// ...
};
enum SshKeyExchangeState {
NoKeyExchange,
KexInitSent,
DhInitSent,
NewKeysSent,
KeyExchangeSuccess // After server's DH_REPLY message
};
class SshConnectionPrivate : public QObject
{
Q_OBJECT
friend class QSsh::SshConnection;
public:
SshConnectionPrivate(SshConnection *conn,
const SshConnectionParameters &serverInfo);
~SshConnectionPrivate();
void connectToHost();
void closeConnection(SshErrorCode sshError, SshError userError,
const QByteArray &serverErrorString, const QString &userErrorString);
QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
QSharedPointer<SshRemoteProcess> createRemoteShell();
QSharedPointer<SftpChannel> createSftpChannel();
QSharedPointer<SshDirectTcpIpTunnel> createDirectTunnel(const QString &originatingHost,
quint16 originatingPort, const QString &remoteHost, quint16 remotePort);
QSharedPointer<SshTcpIpForwardServer> createForwardServer(const QString &remoteHost,
quint16 remotePort);
SshStateInternal state() const { return m_state; }
SshError errorState() const { return m_error; }
QString errorString() const { return m_errorString; }
signals:
void connected();
void disconnected();
void dataAvailable(const QString &message);
void error(QSsh::SshError);
private:
void handleSocketConnected();
void handleIncomingData();
void handleSocketError();
void handleSocketDisconnected();
void handleTimeout();
void sendKeepAlivePacket();
void handleServerId();
void handlePackets();
void handleCurrentPacket();
void handleKeyExchangeInitPacket();
void handleKeyExchangeReplyPacket();
void handleNewKeysPacket();
void handleServiceAcceptPacket();
void handlePasswordExpiredPacket();
void handleUserAuthInfoRequestPacket();
void handleUserAuthSuccessPacket();
void handleUserAuthFailurePacket();
void handleUserAuthBannerPacket();
void handleUnexpectedPacket();
void handleGlobalRequest();
void handleDebugPacket();
void handleUnimplementedPacket();
void handleChannelRequest();
void handleChannelOpen();
void handleChannelOpenFailure();
void handleChannelOpenConfirmation();
void handleChannelSuccess();
void handleChannelFailure();
void handleChannelWindowAdjust();
void handleChannelData();
void handleChannelExtendedData();
void handleChannelEof();
void handleChannelClose();
void handleDisconnect();
void handleRequestSuccess();
void handleRequestFailure();
bool canUseSocket() const;
void createPrivateKey();
void sendData(const QByteArray &data);
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;
bool m_serverHasSentDataBeforeId;
bool m_triedAllPasswordBasedMethods;
};
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,270 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshconnectionmanager.h"
#include "sshconnection.h"
#include <QCoreApplication>
#include <QList>
#include <QMutex>
#include <QMutexLocker>
#include <QObject>
#include <QThread>
#include <QTimer>
namespace QSsh {
namespace Internal {
class UnaquiredConnection {
public:
UnaquiredConnection(SshConnection *conn) : connection(conn), scheduledForRemoval(false) {}
SshConnection *connection;
bool scheduledForRemoval;
};
bool operator==(const UnaquiredConnection &c1, const UnaquiredConnection &c2) {
return c1.connection == c2.connection;
}
bool operator!=(const UnaquiredConnection &c1, const UnaquiredConnection &c2) {
return !(c1 == c2);
}
class SshConnectionManager : public QObject
{
Q_OBJECT
public:
SshConnectionManager()
{
moveToThread(QCoreApplication::instance()->thread());
connect(&m_removalTimer, &QTimer::timeout,
this, &SshConnectionManager::removeInactiveConnections);
m_removalTimer.start(150000); // For a total timeout of five minutes.
}
~SshConnectionManager()
{
foreach (const UnaquiredConnection &connection, m_unacquiredConnections) {
disconnect(connection.connection, 0, this, 0);
delete connection.connection;
}
QSSH_ASSERT(m_acquiredConnections.isEmpty());
QSSH_ASSERT(m_deprecatedConnections.isEmpty());
}
SshConnection *acquireConnection(const SshConnectionParameters &sshParams)
{
QMutexLocker locker(&m_listMutex);
// Check in-use connections:
foreach (SshConnection * const connection, m_acquiredConnections) {
if (connection->connectionParameters() != sshParams)
continue;
if (connection->thread() != QThread::currentThread())
continue;
if (m_deprecatedConnections.contains(connection)) // we were asked to no longer use this one...
continue;
m_acquiredConnections.append(connection);
return connection;
}
// Check cached open connections:
foreach (const UnaquiredConnection &c, m_unacquiredConnections) {
SshConnection * const connection = c.connection;
if (connection->state() != SshConnection::Connected
|| connection->connectionParameters() != sshParams)
continue;
if (connection->thread() != QThread::currentThread()) {
if (connection->channelCount() != 0)
continue;
QMetaObject::invokeMethod(this, "switchToCallerThread",
Qt::BlockingQueuedConnection,
Q_ARG(SshConnection *, connection),
Q_ARG(QObject *, QThread::currentThread()));
}
m_unacquiredConnections.removeOne(c);
m_acquiredConnections.append(connection);
return connection;
}
// create a new connection:
SshConnection * const connection = new SshConnection(sshParams);
connect(connection, &SshConnection::disconnected,
this, &SshConnectionManager::cleanup);
m_acquiredConnections.append(connection);
return connection;
}
void releaseConnection(SshConnection *connection)
{
QMutexLocker locker(&m_listMutex);
const bool wasAquired = m_acquiredConnections.removeOne(connection);
QSSH_ASSERT_AND_RETURN(wasAquired);
if (m_acquiredConnections.contains(connection))
return;
bool doDelete = false;
connection->moveToThread(QCoreApplication::instance()->thread());
if (m_deprecatedConnections.removeOne(connection)
|| connection->state() != SshConnection::Connected) {
doDelete = true;
} else {
QSSH_ASSERT_AND_RETURN(!m_unacquiredConnections.contains(UnaquiredConnection(connection)));
// It can happen that two or more connections with the same parameters were acquired
// if the clients were running in different threads. Only keep one of them in
// such a case.
bool haveConnection = false;
foreach (const UnaquiredConnection &c, m_unacquiredConnections) {
if (c.connection->connectionParameters() == connection->connectionParameters()) {
haveConnection = true;
break;
}
}
if (!haveConnection) {
connection->closeAllChannels(); // Clean up after neglectful clients.
m_unacquiredConnections.append(UnaquiredConnection(connection));
} else {
doDelete = true;
}
}
if (doDelete) {
disconnect(connection, 0, this, 0);
m_deprecatedConnections.removeAll(connection);
connection->deleteLater();
}
}
void forceNewConnection(const SshConnectionParameters &sshParams)
{
QMutexLocker locker(&m_listMutex);
for (int i = 0; i < m_unacquiredConnections.count(); ++i) {
SshConnection * const connection = m_unacquiredConnections.at(i).connection;
if (connection->connectionParameters() == sshParams) {
disconnect(connection, 0, this, 0);
delete connection;
m_unacquiredConnections.removeAt(i);
break;
}
}
foreach (SshConnection * const connection, m_acquiredConnections) {
if (connection->connectionParameters() == sshParams) {
if (!m_deprecatedConnections.contains(connection))
m_deprecatedConnections.append(connection);
}
}
}
private:
Q_INVOKABLE void switchToCallerThread(SshConnection *connection, QObject *threadObj)
{
connection->moveToThread(qobject_cast<QThread *>(threadObj));
}
void cleanup()
{
QMutexLocker locker(&m_listMutex);
SshConnection *currentConnection = qobject_cast<SshConnection *>(sender());
if (!currentConnection)
return;
if (m_unacquiredConnections.removeOne(UnaquiredConnection(currentConnection))) {
disconnect(currentConnection, 0, this, 0);
currentConnection->deleteLater();
}
}
void removeInactiveConnections()
{
QMutexLocker locker(&m_listMutex);
for (int i = m_unacquiredConnections.count() - 1; i >= 0; --i) {
UnaquiredConnection &c = m_unacquiredConnections[i];
if (c.scheduledForRemoval) {
disconnect(c.connection, 0, this, 0);
c.connection->deleteLater();
m_unacquiredConnections.removeAt(i);
} else {
c.scheduledForRemoval = true;
}
}
}
private:
// We expect the number of concurrently open connections to be small.
// If that turns out to not be the case, we can still use a data
// structure with faster access.
QList<UnaquiredConnection> m_unacquiredConnections;
// Can contain the same connection more than once; this acts as a reference count.
QList<SshConnection *> m_acquiredConnections;
QList<SshConnection *> m_deprecatedConnections;
QMutex m_listMutex;
QTimer m_removalTimer;
};
} // namespace Internal
static QMutex instanceMutex;
static Internal::SshConnectionManager &instance()
{
static Internal::SshConnectionManager manager;
return manager;
}
SshConnection *acquireConnection(const SshConnectionParameters &sshParams)
{
QMutexLocker locker(&instanceMutex);
return instance().acquireConnection(sshParams);
}
void releaseConnection(SshConnection *connection)
{
QMutexLocker locker(&instanceMutex);
instance().releaseConnection(connection);
}
void forceNewConnection(const SshConnectionParameters &sshParams)
{
QMutexLocker locker(&instanceMutex);
instance().forceNewConnection(sshParams);
}
} // namespace QSsh
#include "sshconnectionmanager.moc"

View file

@ -0,0 +1,41 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "ssh_global.h"
namespace QSsh {
class SshConnection;
class SshConnectionParameters;
QSSH_EXPORT SshConnection *acquireConnection(const SshConnectionParameters &sshParams);
QSSH_EXPORT void releaseConnection(SshConnection *connection);
// Make sure the next acquireConnection with the given parameters will return a new connection.
QSSH_EXPORT void forceNewConnection(const SshConnectionParameters &sshParams);
} // namespace QSsh

View file

@ -0,0 +1,439 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshcryptofacility_p.h"
#include "sshbotanconversions_p.h"
#include "sshcapabilities_p.h"
#include "sshexception_p.h"
#include "sshkeyexchange_p.h"
#include "sshkeypasswordretriever_p.h"
#include "sshlogging_p.h"
#include "sshpacket_p.h"
#include <botan/botan.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);
}
SshAbstractCryptoFacility::Mode SshAbstractCryptoFacility::getMode(const QByteArray &algoName)
{
if (algoName.endsWith("-ctr"))
return CtrMode;
if (algoName.endsWith("-cbc"))
return CbcMode;
throw SshClientException(SshInternalError, SSH_TR("Unexpected cipher \"%1\"")
.arg(QString::fromLatin1(algoName)));
}
void SshAbstractCryptoFacility::recreateKeys(const SshKeyExchange &kex)
{
checkInvariant();
if (m_sessionId.isEmpty())
m_sessionId = kex.h();
Algorithm_Factory &af = global_state().algorithm_factory();
const QByteArray &rfcCryptAlgoName = cryptAlgoName(kex);
BlockCipher * const cipher
= af.prototype_block_cipher(botanCryptAlgoName(rfcCryptAlgoName))->clone();
m_cipherBlockSize = static_cast<quint32>(cipher->block_size());
const QByteArray ivData = generateHash(kex, ivChar(), m_cipherBlockSize);
const InitializationVector iv(convertByteArray(ivData), m_cipherBlockSize);
const quint32 keySize = static_cast<quint32>(cipher->key_spec().maximum_keylength());
const QByteArray cryptKeyData = generateHash(kex, keyChar(), keySize);
SymmetricKey cryptKey(convertByteArray(cryptKeyData), keySize);
Keyed_Filter * const cipherMode
= makeCipherMode(cipher, getMode(rfcCryptAlgoName), 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);
// Can't use Pipe::LAST_MESSAGE because of a VC bug.
quint32 bytesRead = static_cast<quint32>(m_pipe->read(
reinterpret_cast<byte *>(data.data()) + offset, dataSize, m_pipe->message_count() - 1));
if (bytesRead != dataSize) {
throw SshClientException(SshInternalError,
QLatin1String("Internal error: Botan::Pipe::read() returned unexpected value"));
}
}
Keyed_Filter *SshAbstractCryptoFacility::makeCtrCipherMode(BlockCipher *cipher,
const InitializationVector &iv, const SymmetricKey &key)
{
StreamCipher_Filter * const filter = new StreamCipher_Filter(new CTR_BE(cipher));
filter->set_key(key);
filter->set_iv(iv);
return filter;
}
QByteArray SshAbstractCryptoFacility::generateMac(const QByteArray &data,
quint32 dataSize) const
{
return m_sessionId.isEmpty()
? QByteArray()
: convertByteArray(m_hMac->process(reinterpret_cast<const byte *>(data.constData()),
dataSize));
}
QByteArray SshAbstractCryptoFacility::generateHash(const SshKeyExchange &kex,
char c, quint32 length)
{
const QByteArray &k = kex.k();
const QByteArray &h = kex.h();
QByteArray data(k);
data.append(h).append(c).append(m_sessionId);
SecureVector<byte> key
= kex.hash()->process(convertByteArray(data), data.size());
while (key.size() < length) {
SecureVector<byte> tmpKey;
tmpKey += SecureVector<byte>(convertByteArray(k), k.size());
tmpKey += SecureVector<byte>(convertByteArray(h), h.size());
tmpKey += key;
key += 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-----");
const QByteArray SshEncryptionFacility::PrivKeyFileStartLineEcdsa("-----BEGIN EC PRIVATE KEY-----");
const QByteArray SshEncryptionFacility::PrivKeyFileEndLineEcdsa("-----END EC PRIVATE KEY-----");
QByteArray SshEncryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
{
return kex.encryptionAlgo();
}
QByteArray SshEncryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
{
return kex.hMacAlgoClientToServer();
}
Keyed_Filter *SshEncryptionFacility::makeCipherMode(BlockCipher *cipher, Mode mode,
const InitializationVector &iv, const SymmetricKey &key)
{
switch (mode) {
case CbcMode:
return new CBC_Encryption(cipher, new Null_Padding, key, iv);
case CtrMode:
return makeCtrCipherMode(cipher, iv, key);
}
return 0; // For dumb compilers.
}
void SshEncryptionFacility::encrypt(QByteArray &data) const
{
convert(data, 0, data.size());
}
void SshEncryptionFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
{
if (privKeyFileContents == m_cachedPrivKeyContents)
return;
m_authKeyAlgoName.clear();
qCDebug(sshLog, "%s: Key not cached, reading", Q_FUNC_INFO);
QList<BigInt> pubKeyParams;
QList<BigInt> allKeyParams;
QString error1;
QString error2;
if (!createAuthenticationKeyFromPKCS8(privKeyFileContents, pubKeyParams, allKeyParams, error1)
&& !createAuthenticationKeyFromOpenSSL(privKeyFileContents, pubKeyParams, allKeyParams,
error2)) {
qCDebug(sshLog, "%s: %s\n\t%s\n", Q_FUNC_INFO, qPrintable(error1), qPrintable(error2));
throw SshClientException(SshKeyFileError, SSH_TR("Decoding of private key file failed: "
"Format not understood."));
}
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);
auto * const ecdsaKey = dynamic_cast<ECDSA_PrivateKey *>(m_authKey.data());
if (ecdsaKey) {
m_authPubKeyBlob += AbstractSshPacket::encodeString(m_authKeyAlgoName.mid(11)); // Without "ecdsa-sha2-" prefix.
m_authPubKeyBlob += AbstractSshPacket::encodeString(
convertByteArray(EC2OSP(ecdsaKey->public_point(), PointGFp::UNCOMPRESSED)));
} else {
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());
m_authKey.reset(PKCS8::load_key(pipe, m_rng, SshKeyPasswordRetriever()));
if (auto * const dsaKey = dynamic_cast<DSA_PrivateKey *>(m_authKey.data())) {
m_authKeyAlgoName = SshCapabilities::PubKeyDss;
pubKeyParams << dsaKey->group_p() << dsaKey->group_q()
<< dsaKey->group_g() << dsaKey->get_y();
allKeyParams << pubKeyParams << dsaKey->get_x();
} else if (auto * const rsaKey = dynamic_cast<RSA_PrivateKey *>(m_authKey.data())) {
m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
pubKeyParams << rsaKey->get_e() << rsaKey->get_n();
allKeyParams << pubKeyParams << rsaKey->get_p() << rsaKey->get_q()
<< rsaKey->get_d();
} else if (auto * const ecdsaKey = dynamic_cast<ECDSA_PrivateKey *>(m_authKey.data())) {
const BigInt value = ecdsaKey->private_value();
m_authKeyAlgoName = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(
static_cast<int>(value.bytes()));
pubKeyParams << ecdsaKey->public_point().get_affine_x()
<< ecdsaKey->public_point().get_affine_y();
allKeyParams << pubKeyParams << value;
} else {
qCWarning(sshLog, "%s: Unexpected code flow, expected success or exception.",
Q_FUNC_INFO);
return false;
}
} catch (const std::exception &ex) {
error = QLatin1String(ex.what());
return false;
}
return true;
}
bool SshEncryptionFacility::createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
{
try {
bool syntaxOk = true;
QList<QByteArray> lines = privKeyFileContents.split('\n');
while (lines.last().isEmpty())
lines.removeLast();
if (lines.count() < 3) {
syntaxOk = false;
} else if (lines.first() == PrivKeyFileStartLineRsa) {
if (lines.last() != PrivKeyFileEndLineRsa)
syntaxOk = false;
else
m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
} else if (lines.first() == PrivKeyFileStartLineDsa) {
if (lines.last() != PrivKeyFileEndLineDsa)
syntaxOk = false;
else
m_authKeyAlgoName = SshCapabilities::PubKeyDss;
} else if (lines.first() == PrivKeyFileStartLineEcdsa) {
if (lines.last() != PrivKeyFileEndLineEcdsa)
syntaxOk = false;
// m_authKeyAlgoName set below, as we don't know the size yet.
} else {
syntaxOk = false;
}
if (!syntaxOk) {
error = SSH_TR("Unexpected format.");
return false;
}
QByteArray privateKeyBlob;
for (int i = 1; i < lines.size() - 1; ++i)
privateKeyBlob += lines.at(i);
privateKeyBlob = QByteArray::fromBase64(privateKeyBlob);
BER_Decoder decoder(convertByteArray(privateKeyBlob), privateKeyBlob.size());
BER_Decoder sequence = decoder.start_cons(SEQUENCE);
size_t version;
sequence.decode (version);
const size_t expectedVersion = m_authKeyAlgoName.isEmpty() ? 1 : 0;
if (version != expectedVersion) {
error = SSH_TR("Key encoding has version %1, expected %2.")
.arg(version).arg(expectedVersion);
return false;
}
if (m_authKeyAlgoName == SshCapabilities::PubKeyDss) {
BigInt p, q, g, y, x;
sequence.decode (p).decode (q).decode (g).decode (y).decode (x);
DSA_PrivateKey * const dsaKey = new DSA_PrivateKey(m_rng, DL_Group(p, q, g), x);
m_authKey.reset(dsaKey);
pubKeyParams << p << q << g << y;
allKeyParams << pubKeyParams << x;
} else if (m_authKeyAlgoName == SshCapabilities::PubKeyRsa) {
BigInt p, q, e, d, n;
sequence.decode(n).decode(e).decode(d).decode(p).decode(q);
RSA_PrivateKey * const rsaKey = new RSA_PrivateKey(m_rng, p, q, e, d, n);
m_authKey.reset(rsaKey);
pubKeyParams << e << n;
allKeyParams << pubKeyParams << p << q << d;
} else {
BigInt privKey;
sequence.decode_octet_string_bigint(privKey);
m_authKeyAlgoName = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(
static_cast<int>(privKey.bytes()));
const EC_Group group(SshCapabilities::oid(m_authKeyAlgoName));
auto * const key = new ECDSA_PrivateKey(m_rng, group, privKey);
m_authKey.reset(key);
pubKeyParams << key->public_point().get_affine_x()
<< key->public_point().get_affine_y();
allKeyParams << pubKeyParams << privKey;
}
sequence.discard_remaining();
sequence.verify_end();
} catch (const std::exception &ex) {
error = QLatin1String(ex.what());
return false;
}
return true;
}
QByteArray SshEncryptionFacility::authenticationAlgorithmName() const
{
Q_ASSERT(m_authKey);
return m_authKeyAlgoName;
}
QByteArray SshEncryptionFacility::authenticationKeySignature(const QByteArray &data) const
{
Q_ASSERT(m_authKey);
QScopedPointer<PK_Signer> signer(new PK_Signer(*m_authKey,
botanEmsaAlgoName(m_authKeyAlgoName)));
QByteArray dataToSign = AbstractSshPacket::encodeString(sessionId()) + data;
QByteArray signature
= convertByteArray(signer->sign_message(convertByteArray(dataToSign),
dataToSign.size(), m_rng));
if (m_authKeyAlgoName.startsWith(SshCapabilities::PubKeyEcdsaPrefix)) {
// The Botan output is not quite in the format that SSH defines.
const int halfSize = signature.count() / 2;
const BigInt r = BigInt::decode(convertByteArray(signature), halfSize);
const BigInt s = BigInt::decode(convertByteArray(signature.mid(halfSize)), halfSize);
signature = AbstractSshPacket::encodeMpInt(r) + AbstractSshPacket::encodeMpInt(s);
}
return AbstractSshPacket::encodeString(m_authKeyAlgoName)
+ AbstractSshPacket::encodeString(signature);
}
QByteArray SshEncryptionFacility::getRandomNumbers(int count) const
{
QByteArray data;
data.resize(count);
m_rng.randomize(convertByteArray(data), count);
return data;
}
SshEncryptionFacility::~SshEncryptionFacility() {}
QByteArray SshDecryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
{
return kex.decryptionAlgo();
}
QByteArray SshDecryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
{
return kex.hMacAlgoServerToClient();
}
Keyed_Filter *SshDecryptionFacility::makeCipherMode(BlockCipher *cipher, Mode mode, const InitializationVector &iv,
const SymmetricKey &key)
{
switch (mode) {
case CbcMode:
return new CBC_Decryption(cipher, new Null_Padding, key, iv);
case CtrMode:
return makeCtrCipherMode(cipher, iv, key);
}
return 0; // For dumb compilers.
}
void SshDecryptionFacility::decrypt(QByteArray &data, quint32 offset,
quint32 dataSize) const
{
convert(data, offset, dataSize);
qCDebug(sshLog, "Decrypted data:");
const char * const start = data.constData() + offset;
const char * const end = start + dataSize;
for (const char *c = start; c < end; ++c)
qCDebug(sshLog, ) << "'" << *c << "' (0x" << (static_cast<int>(*c) & 0xff) << ")";
}
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,138 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <botan/botan.h>
#include <QByteArray>
#include <QScopedPointer>
namespace QSsh {
namespace Internal {
class SshKeyExchange;
class SshAbstractCryptoFacility
{
public:
virtual ~SshAbstractCryptoFacility();
void clearKeys();
void recreateKeys(const SshKeyExchange &kex);
QByteArray generateMac(const QByteArray &data, quint32 dataSize) const;
quint32 cipherBlockSize() const { return m_cipherBlockSize; }
quint32 macLength() const { return m_macLength; }
protected:
enum Mode { CbcMode, CtrMode };
SshAbstractCryptoFacility();
void convert(QByteArray &data, quint32 offset, quint32 dataSize) const;
QByteArray sessionId() const { return m_sessionId; }
Botan::Keyed_Filter *makeCtrCipherMode(Botan::BlockCipher *cipher,
const Botan::InitializationVector &iv, const Botan::SymmetricKey &key);
private:
SshAbstractCryptoFacility(const SshAbstractCryptoFacility &);
SshAbstractCryptoFacility &operator=(const SshAbstractCryptoFacility &);
virtual QByteArray cryptAlgoName(const SshKeyExchange &kex) const = 0;
virtual QByteArray hMacAlgoName(const SshKeyExchange &kex) const = 0;
virtual Botan::Keyed_Filter *makeCipherMode(Botan::BlockCipher *cipher,
Mode mode, 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;
static Mode getMode(const QByteArray &algoName);
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::Keyed_Filter *makeCipherMode(Botan::BlockCipher *cipher,
Mode mode, 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;
static const QByteArray PrivKeyFileStartLineEcdsa;
static const QByteArray PrivKeyFileEndLineEcdsa;
QByteArray m_authKeyAlgoName;
QByteArray m_authPubKeyBlob;
QByteArray m_cachedPrivKeyContents;
QScopedPointer<Botan::Private_Key> m_authKey;
mutable Botan::AutoSeeded_RNG m_rng;
};
class SshDecryptionFacility : public SshAbstractCryptoFacility
{
public:
void decrypt(QByteArray &data, quint32 offset, quint32 dataSize) const;
private:
virtual QByteArray cryptAlgoName(const SshKeyExchange &kex) const;
virtual QByteArray hMacAlgoName(const SshKeyExchange &kex) const;
virtual Botan::Keyed_Filter *makeCipherMode(Botan::BlockCipher *cipher,
Mode mode, 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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,37 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#define SSHERRORS_P_H
namespace QSsh {
enum SshError {
SshNoError, SshSocketError, SshTimeoutError, SshProtocolError,
SshHostKeyError, SshKeyFileError, SshAuthenticationError,
SshClosedByServerError, SshInternalError
};
} // namespace QSsh

View file

@ -0,0 +1,82 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,66 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "ssh_global.h"
#include <QSharedPointer>
QT_BEGIN_NAMESPACE
class QByteArray;
class QString;
QT_END_NAMESPACE
namespace QSsh {
class SshHostKeyDatabase;
typedef QSharedPointer<SshHostKeyDatabase> SshHostKeyDatabasePtr;
class QSSH_EXPORT SshHostKeyDatabase
{
friend class QSharedPointer<SshHostKeyDatabase>; // To give create() access to our constructor.
public:
enum KeyLookupResult {
KeyLookupMatch,
KeyLookupNoMatch,
KeyLookupMismatch
};
~SshHostKeyDatabase();
bool load(const QString &filePath, QString *error = 0);
bool store(const QString &filePath, QString *error = 0) const;
KeyLookupResult matchHostKey(const QString &hostName, const QByteArray &key) const;
void insertHostKey(const QString &hostName, const QByteArray &key);
private:
SshHostKeyDatabase();
class SshHostKeyDatabasePrivate;
SshHostKeyDatabasePrivate * const d;
};
} // namespace QSsh

View file

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

View file

@ -0,0 +1,213 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "sshpacket_p.h"
#include "sshcryptofacility_p.h"
#include "sshpacketparser_p.h"
#include <QStringList>
namespace QSsh {
namespace Internal {
class SshKeyExchange;
struct SshKeyExchangeInit
{
char cookie[16];
SshNameList keyAlgorithms;
SshNameList serverHostKeyAlgorithms;
SshNameList encryptionAlgorithmsClientToServer;
SshNameList encryptionAlgorithmsServerToClient;
SshNameList macAlgorithmsClientToServer;
SshNameList macAlgorithmsServerToClient;
SshNameList compressionAlgorithmsClientToServer;
SshNameList compressionAlgorithmsServerToClient;
SshNameList languagesClientToServer;
SshNameList languagesServerToClient;
bool firstKexPacketFollows;
};
struct SshKeyExchangeReply
{
QByteArray k_s;
QList<Botan::BigInt> hostKeyParameters; // DSS: p, q, g, y. RSA: e, n.
QByteArray q; // For ECDSA host keys only.
Botan::BigInt f; // For DH only.
QByteArray q_s; // For ECDH only.
QByteArray signatureBlob;
};
struct SshDisconnect
{
quint32 reasonCode;
QString description;
QByteArray language;
};
struct SshUserAuthBanner
{
QString message;
QByteArray language;
};
struct SshUserAuthInfoRequestPacket
{
QString name;
QString instruction;
QByteArray languageTag;
QStringList prompts;
QList<bool> echos;
};
struct SshDebug
{
bool display;
QString message;
QByteArray language;
};
struct SshUnimplemented
{
quint32 invalidMsgSeqNr;
};
struct SshRequestSuccess
{
quint32 bindPort;
};
struct SshChannelOpen
{
quint32 remoteChannel;
quint32 remoteWindowSize;
quint32 remoteMaxPacketSize;
QByteArray remoteAddress;
quint32 remotePort;
};
struct SshChannelOpenFailure
{
quint32 localChannel;
quint32 reasonCode;
QString reasonString;
QByteArray language;
};
struct SshChannelOpenConfirmation
{
quint32 localChannel;
quint32 remoteChannel;
quint32 remoteWindowSize;
quint32 remoteMaxPacketSize;
};
struct SshChannelWindowAdjust
{
quint32 localChannel;
quint32 bytesToAdd;
};
struct SshChannelData
{
quint32 localChannel;
QByteArray data;
};
struct SshChannelExtendedData
{
quint32 localChannel;
quint32 type;
QByteArray data;
};
struct SshChannelExitStatus
{
quint32 localChannel;
quint32 exitStatus;
};
struct SshChannelExitSignal
{
quint32 localChannel;
QByteArray signal;
bool coreDumped;
QString error;
QByteArray language;
};
class SshIncomingPacket : public AbstractSshPacket
{
public:
SshIncomingPacket();
void consumeData(QByteArray &data);
void recreateKeys(const SshKeyExchange &keyExchange);
void reset();
SshKeyExchangeInit extractKeyExchangeInitData() const;
SshKeyExchangeReply extractKeyExchangeReply(const QByteArray &kexAlgo,
const QByteArray &hostKeyAlgo) const;
SshDisconnect extractDisconnect() const;
SshUserAuthBanner extractUserAuthBanner() const;
SshUserAuthInfoRequestPacket extractUserAuthInfoRequest() const;
SshDebug extractDebug() const;
SshRequestSuccess extractRequestSuccess() const;
SshUnimplemented extractUnimplemented() const;
SshChannelOpen extractChannelOpen() 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;
static const QByteArray ForwardedTcpIpType;
private:
virtual quint32 cipherBlockSize() const;
virtual quint32 macLength() const;
virtual void calculateLength() const;
void decrypt();
void moveFirstBytes(QByteArray &target, QByteArray &source, int n);
quint32 m_serverSeqNr;
SshDecryptionFacility m_decrypter;
};
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,49 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshinit_p.h"
#include <botan/botan.h>
#include <QMutex>
#include <QMutexLocker>
namespace QSsh {
namespace Internal {
static bool initialized = false;
static QMutex initMutex;
void initSsh()
{
QMutexLocker locker(&initMutex);
if (!initialized) {
Botan::LibraryInitializer::initialize("thread_safe=true");
initialized = true;
}
}
} // namespace Internal
} // namespace QSsh

View file

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

View file

@ -0,0 +1,174 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshkeycreationdialog.h"
#include "ui_sshkeycreationdialog.h"
#include "sshkeygenerator.h"
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QApplication>
#include <QMessageBox>
#include <QStandardPaths>
namespace QSsh {
SshKeyCreationDialog::SshKeyCreationDialog(QWidget *parent)
: QDialog(parent), m_keyGenerator(0), m_ui(new Ui::SshKeyCreationDialog)
{
m_ui->setupUi(this);
// Not using Utils::PathChooser::browseButtonLabel to avoid dependency
#ifdef Q_OS_MAC
m_ui->privateKeyFileButton->setText(tr("Choose..."));
#else
m_ui->privateKeyFileButton->setText(tr("Browse..."));
#endif
const QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation)
+ QLatin1String("/.ssh/qtc_id");
setPrivateKeyFile(defaultPath);
connect(m_ui->rsa, &QRadioButton::toggled,
this, &SshKeyCreationDialog::keyTypeChanged);
connect(m_ui->dsa, &QRadioButton::toggled,
this, &SshKeyCreationDialog::keyTypeChanged);
connect(m_ui->privateKeyFileButton, &QPushButton::clicked,
this, &SshKeyCreationDialog::handleBrowseButtonClicked);
connect(m_ui->generateButton, &QPushButton::clicked,
this, &SshKeyCreationDialog::generateKeys);
keyTypeChanged();
}
SshKeyCreationDialog::~SshKeyCreationDialog()
{
delete m_keyGenerator;
delete m_ui;
}
void SshKeyCreationDialog::keyTypeChanged()
{
m_ui->comboBox->clear();
QStringList keySizes;
if (m_ui->rsa->isChecked())
keySizes << QLatin1String("1024") << QLatin1String("2048") << QLatin1String("4096");
else if (m_ui->ecdsa->isChecked())
keySizes << QLatin1String("256") << QLatin1String("384") << QLatin1String("521");
else if (m_ui->dsa->isChecked())
keySizes << QLatin1String("1024");
m_ui->comboBox->addItems(keySizes);
if (!keySizes.isEmpty())
m_ui->comboBox->setCurrentIndex(0);
m_ui->comboBox->setEnabled(!keySizes.isEmpty());
}
void SshKeyCreationDialog::generateKeys()
{
if (userForbidsOverwriting())
return;
const SshKeyGenerator::KeyType keyType = m_ui->rsa->isChecked()
? SshKeyGenerator::Rsa : m_ui->dsa->isChecked()
? SshKeyGenerator::Dsa : SshKeyGenerator::Ecdsa;
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("Cannot 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("Cannot Save Private Key File"),
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("Cannot Save Public Key File"),
tr("The public key file could not be saved: %1").arg(publicKeyFile.errorString()));
return;
}
accept();
}
bool SshKeyCreationDialog::userForbidsOverwriting()
{
if (!QFileInfo::exists(privateKeyFilePath()) && !QFileInfo::exists(publicKeyFilePath()))
return false;
const QMessageBox::StandardButton reply = QMessageBox::question(this, tr("File Exists"),
tr("There already is a file of that name. Do you want to overwrite it?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
return reply != QMessageBox::Yes;
}
QString SshKeyCreationDialog::privateKeyFilePath() const
{
return m_ui->privateKeyFileValueLabel->text();
}
QString SshKeyCreationDialog::publicKeyFilePath() const
{
return m_ui->publicKeyFileLabel->text();
}
} // namespace QSsh

View file

@ -0,0 +1,60 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "ssh_global.h"
#include <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:
void keyTypeChanged();
void generateKeys();
void handleBrowseButtonClicked();
void setPrivateKeyFile(const QString &filePath);
void saveKeys();
bool userForbidsOverwriting();
private:
SshKeyGenerator *m_keyGenerator;
Ui::SshKeyCreationDialog *m_ui;
};
} // namespace QSsh

View file

@ -0,0 +1,264 @@
<?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>380</width>
<height>231</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>SSH Key Configuration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Options</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="keyAlgo">
<property name="text">
<string>Key algorithm:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QRadioButton" name="rsa">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;RSA</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="dsa">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;DSA</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="ecdsa">
<property name="text">
<string>ECDSA</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="keySize">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Key &amp;size:</string>
</property>
<property name="buddy">
<cstring>comboBox</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QComboBox" name="comboBox">
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="privateKeyFileLabel">
<property name="text">
<string>Private key file:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="privateKeyFileValueLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="privateKeyFileButton">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Public key file:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="publicKeyFileLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<item>
<widget class="QPushButton" name="generateButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Generate And Save Key Pair</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="closeButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Cancel</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>closeButton</sender>
<signal>clicked()</signal>
<receiver>QSsh::SshKeyCreationDialog</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>195</x>
<y>184</y>
</hint>
<hint type="destinationlabel">
<x>381</x>
<y>107</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -0,0 +1,273 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshkeyexchange_p.h"
#include "ssh_global.h"
#include "sshbotanconversions_p.h"
#include "sshcapabilities_p.h"
#include "sshlogging_p.h"
#include "sshsendfacility_p.h"
#include "sshexception_p.h"
#include "sshincomingpacket_p.h"
#include <botan/botan.h>
#include <string>
using namespace Botan;
namespace QSsh {
namespace Internal {
namespace {
// For debugging
void printNameList(const char *listName, const SshNameList &list)
{
qCDebug(sshLog, "%s:", listName);
foreach (const QByteArray &name, list.names)
qCDebug(sshLog, "%s", name.constData());
}
void printData(const char *name, const QByteArray &data)
{
qCDebug(sshLog, "The client thinks the %s has length %d and is: %s", name, data.count(),
data.toHex().constData());
}
} // anonymous namespace
SshKeyExchange::SshKeyExchange(const SshConnectionParameters &connParams,
SshSendFacility &sendFacility)
: m_connParams(connParams), m_sendFacility(sendFacility)
{
}
SshKeyExchange::~SshKeyExchange() {}
void SshKeyExchange::sendKexInitPacket(const QByteArray &serverId)
{
m_serverId = serverId;
m_clientKexInitPayload = m_sendFacility.sendKeyExchangeInitPacket();
}
bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit)
{
qCDebug(sshLog, "server requests key exchange");
serverKexInit.printRawBytes();
SshKeyExchangeInit kexInitParams
= serverKexInit.extractKeyExchangeInitData();
printNameList("Key Algorithms", kexInitParams.keyAlgorithms);
printNameList("Server Host Key Algorithms", kexInitParams.serverHostKeyAlgorithms);
printNameList("Encryption algorithms client to server", kexInitParams.encryptionAlgorithmsClientToServer);
printNameList("Encryption algorithms server to client", kexInitParams.encryptionAlgorithmsServerToClient);
printNameList("MAC algorithms client to server", kexInitParams.macAlgorithmsClientToServer);
printNameList("MAC algorithms server to client", kexInitParams.macAlgorithmsServerToClient);
printNameList("Compression algorithms client to server", kexInitParams.compressionAlgorithmsClientToServer);
printNameList("Compression algorithms client to server", kexInitParams.compressionAlgorithmsClientToServer);
printNameList("Languages client to server", kexInitParams.languagesClientToServer);
printNameList("Languages server to client", kexInitParams.languagesServerToClient);
qCDebug(sshLog, "First packet follows: %d", kexInitParams.firstKexPacketFollows);
m_kexAlgoName = SshCapabilities::findBestMatch(SshCapabilities::KeyExchangeMethods,
kexInitParams.keyAlgorithms.names);
m_serverHostKeyAlgo = SshCapabilities::findBestMatch(SshCapabilities::PublicKeyAlgorithms,
kexInitParams.serverHostKeyAlgorithms.names);
determineHashingAlgorithm(kexInitParams, true);
determineHashingAlgorithm(kexInitParams, false);
m_encryptionAlgo
= SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms,
kexInitParams.encryptionAlgorithmsClientToServer.names);
m_decryptionAlgo
= SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms,
kexInitParams.encryptionAlgorithmsServerToClient.names);
SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms,
kexInitParams.compressionAlgorithmsClientToServer.names);
SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms,
kexInitParams.compressionAlgorithmsServerToClient.names);
AutoSeeded_RNG rng;
if (m_kexAlgoName.startsWith(SshCapabilities::EcdhKexNamePrefix)) {
m_ecdhKey.reset(new ECDH_PrivateKey(rng, EC_Group(botanKeyExchangeAlgoName(m_kexAlgoName))));
m_sendFacility.sendKeyEcdhInitPacket(convertByteArray(m_ecdhKey->public_value()));
} else {
m_dhKey.reset(new DH_PrivateKey(rng, DL_Group(botanKeyExchangeAlgoName(m_kexAlgoName))));
m_sendFacility.sendKeyDhInitPacket(m_dhKey->get_y());
}
m_serverKexInitPayload = serverKexInit.payLoad();
return kexInitParams.firstKexPacketFollows;
}
void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply,
const QByteArray &clientId)
{
const SshKeyExchangeReply &reply
= dhReply.extractKeyExchangeReply(m_kexAlgoName, m_serverHostKeyAlgo);
if (m_dhKey && (reply.f <= 0 || reply.f >= m_dhKey->group_p())) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
"Server sent invalid f.");
}
QByteArray concatenatedData = AbstractSshPacket::encodeString(clientId);
concatenatedData += AbstractSshPacket::encodeString(m_serverId);
concatenatedData += AbstractSshPacket::encodeString(m_clientKexInitPayload);
concatenatedData += AbstractSshPacket::encodeString(m_serverKexInitPayload);
concatenatedData += reply.k_s;
printData("Client Id", AbstractSshPacket::encodeString(clientId));
printData("Server Id", AbstractSshPacket::encodeString(m_serverId));
printData("Client Payload", AbstractSshPacket::encodeString(m_clientKexInitPayload));
printData("Server payload", AbstractSshPacket::encodeString(m_serverKexInitPayload));
printData("K_S", reply.k_s);
SecureVector<byte> encodedK;
if (m_dhKey) {
concatenatedData += AbstractSshPacket::encodeMpInt(m_dhKey->get_y());
concatenatedData += AbstractSshPacket::encodeMpInt(reply.f);
DH_KA_Operation dhOp(*m_dhKey);
SecureVector<byte> encodedF = BigInt::encode(reply.f);
encodedK = dhOp.agree(encodedF, encodedF.size());
printData("y", AbstractSshPacket::encodeMpInt(m_dhKey->get_y()));
printData("f", AbstractSshPacket::encodeMpInt(reply.f));
m_dhKey.reset(nullptr);
} else {
Q_ASSERT(m_ecdhKey);
concatenatedData // Q_C.
+= AbstractSshPacket::encodeString(convertByteArray(m_ecdhKey->public_value()));
concatenatedData += AbstractSshPacket::encodeString(reply.q_s);
ECDH_KA_Operation ecdhOp(*m_ecdhKey);
encodedK = ecdhOp.agree(convertByteArray(reply.q_s), reply.q_s.count());
m_ecdhKey.reset(nullptr);
}
const BigInt k = BigInt::decode(encodedK);
m_k = AbstractSshPacket::encodeMpInt(k); // Roundtrip, as Botan encodes BigInts somewhat differently.
printData("K", m_k);
concatenatedData += m_k;
printData("Concatenated data", concatenatedData);
m_hash.reset(get_hash(botanHMacAlgoName(hashAlgoForKexAlgo())));
const SecureVector<byte> &hashResult = m_hash->process(convertByteArray(concatenatedData),
concatenatedData.size());
m_h = convertByteArray(hashResult);
printData("H", m_h);
QScopedPointer<Public_Key> sigKey;
if (m_serverHostKeyAlgo == SshCapabilities::PubKeyDss) {
const DL_Group group(reply.hostKeyParameters.at(0), reply.hostKeyParameters.at(1),
reply.hostKeyParameters.at(2));
DSA_PublicKey * const dsaKey
= new DSA_PublicKey(group, reply.hostKeyParameters.at(3));
sigKey.reset(dsaKey);
} else if (m_serverHostKeyAlgo == SshCapabilities::PubKeyRsa) {
RSA_PublicKey * const rsaKey
= new RSA_PublicKey(reply.hostKeyParameters.at(1), reply.hostKeyParameters.at(0));
sigKey.reset(rsaKey);
} else {
QSSH_ASSERT_AND_RETURN(m_serverHostKeyAlgo.startsWith(SshCapabilities::PubKeyEcdsaPrefix));
const EC_Group domain(SshCapabilities::oid(m_serverHostKeyAlgo));
const PointGFp point = OS2ECP(convertByteArray(reply.q), reply.q.count(),
domain.get_curve());
ECDSA_PublicKey * const ecdsaKey = new ECDSA_PublicKey(domain, point);
sigKey.reset(ecdsaKey);
}
const byte * const botanH = convertByteArray(m_h);
const Botan::byte * const botanSig = convertByteArray(reply.signatureBlob);
PK_Verifier verifier(*sigKey, botanEmsaAlgoName(m_serverHostKeyAlgo));
if (!verifier.verify_message(botanH, m_h.size(), botanSig, reply.signatureBlob.size())) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
"Invalid signature in key exchange reply packet.");
}
checkHostKey(reply.k_s);
m_sendFacility.sendNewKeysPacket();
}
QByteArray SshKeyExchange::hashAlgoForKexAlgo() const
{
if (m_kexAlgoName == SshCapabilities::EcdhNistp256)
return SshCapabilities::HMacSha256;
if (m_kexAlgoName == SshCapabilities::EcdhNistp384)
return SshCapabilities::HMacSha384;
if (m_kexAlgoName == SshCapabilities::EcdhNistp521)
return SshCapabilities::HMacSha512;
return SshCapabilities::HMacSha1;
}
void SshKeyExchange::determineHashingAlgorithm(const SshKeyExchangeInit &kexInit,
bool serverToClient)
{
QByteArray * const algo = serverToClient ? &m_s2cHMacAlgo : &m_c2sHMacAlgo;
const QList<QByteArray> &serverCapabilities = serverToClient
? kexInit.macAlgorithmsServerToClient.names
: kexInit.macAlgorithmsClientToServer.names;
*algo = SshCapabilities::findBestMatch(SshCapabilities::MacAlgorithms, serverCapabilities);
}
void SshKeyExchange::checkHostKey(const QByteArray &hostKey)
{
if (m_connParams.hostKeyCheckingMode == SshHostKeyCheckingNone) {
if (m_connParams.hostKeyDatabase)
m_connParams.hostKeyDatabase->insertHostKey(m_connParams.host, hostKey);
return;
}
if (!m_connParams.hostKeyDatabase) {
throw SshClientException(SshInternalError,
SSH_TR("Host key database must exist "
"if host key checking is enabled."));
}
switch (m_connParams.hostKeyDatabase->matchHostKey(m_connParams.host, hostKey)) {
case SshHostKeyDatabase::KeyLookupMatch:
return; // Nothing to do.
case SshHostKeyDatabase::KeyLookupMismatch:
if (m_connParams.hostKeyCheckingMode != SshHostKeyCheckingAllowMismatch)
throwHostKeyException();
break;
case SshHostKeyDatabase::KeyLookupNoMatch:
if (m_connParams.hostKeyCheckingMode == SshHostKeyCheckingStrict)
throwHostKeyException();
break;
}
m_connParams.hostKeyDatabase->insertHostKey(m_connParams.host, hostKey);
}
void SshKeyExchange::throwHostKeyException()
{
throw SshServerException(SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, "Host key changed",
SSH_TR("Host key of machine \"%1\" has changed.")
.arg(m_connParams.host));
}
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,93 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "sshconnection.h"
#include <QByteArray>
#include <QScopedPointer>
namespace Botan {
class DH_PrivateKey;
class ECDH_PrivateKey;
class HashFunction;
}
namespace QSsh {
namespace Internal {
struct SshKeyExchangeInit;
class SshSendFacility;
class SshIncomingPacket;
class SshKeyExchange
{
public:
SshKeyExchange(const SshConnectionParameters &connParams, SshSendFacility &sendFacility);
~SshKeyExchange();
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 hashAlgoForKexAlgo() const;
void determineHashingAlgorithm(const SshKeyExchangeInit &kexInit, bool serverToClient);
void checkHostKey(const QByteArray &hostKey);
Q_NORETURN void throwHostKeyException();
QByteArray m_serverId;
QByteArray m_clientKexInitPayload;
QByteArray m_serverKexInitPayload;
QScopedPointer<Botan::DH_PrivateKey> m_dhKey;
QScopedPointer<Botan::ECDH_PrivateKey> m_ecdhKey;
QByteArray m_kexAlgoName;
QByteArray m_k;
QByteArray m_h;
QByteArray m_serverHostKeyAlgo;
QByteArray m_encryptionAlgo;
QByteArray m_decryptionAlgo;
QByteArray m_c2sHMacAlgo;
QByteArray m_s2cHMacAlgo;
QScopedPointer<Botan::HashFunction> m_hash;
const SshConnectionParameters m_connParams;
SshSendFacility &m_sendFacility;
};
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,226 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshkeygenerator.h"
#include "sshbotanconversions_p.h"
#include "sshcapabilities_p.h"
#include "ssh_global.h"
#include "sshinit_p.h"
#include "sshpacket_p.h"
#include <botan/botan.h>
#include <QDateTime>
#include <QInputDialog>
#include <string>
namespace QSsh {
using namespace Botan;
using namespace Internal;
SshKeyGenerator::SshKeyGenerator() : m_type(Rsa)
{
initSsh();
}
bool SshKeyGenerator::generateKeys(KeyType type, PrivateKeyFormat format, int keySize,
EncryptionMode encryptionMode)
{
m_type = type;
m_encryptionMode = encryptionMode;
try {
AutoSeeded_RNG rng;
KeyPtr key;
switch (m_type) {
case Rsa:
key = KeyPtr(new RSA_PrivateKey(rng, keySize));
break;
case Dsa:
key = KeyPtr(new DSA_PrivateKey(rng, DL_Group(rng, DL_Group::DSA_Kosherizer, keySize)));
break;
case Ecdsa: {
const QByteArray algo = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(keySize / 8);
key = KeyPtr(new ECDSA_PrivateKey(rng, EC_Group(SshCapabilities::oid(algo))));
break;
}
}
switch (format) {
case Pkcs8:
generatePkcs8KeyStrings(key, rng);
break;
case OpenSsl:
generateOpenSslKeyStrings(key);
break;
case Mixed:
default:
generatePkcs8KeyString(key, true, rng);
generateOpenSslPublicKeyString(key);
}
return true;
} catch (const std::exception &e) {
m_error = tr("Error generating key: %1").arg(QString::fromLatin1(e.what()));
return false;
}
}
void SshKeyGenerator::generatePkcs8KeyStrings(const KeyPtr &key, RandomNumberGenerator &rng)
{
generatePkcs8KeyString(key, false, rng);
generatePkcs8KeyString(key, true, rng);
}
void SshKeyGenerator::generatePkcs8KeyString(const KeyPtr &key, bool privateKey,
RandomNumberGenerator &rng)
{
Pipe pipe;
pipe.start_msg();
QByteArray *keyData;
if (privateKey) {
QString password;
if (m_encryptionMode == DoOfferEncryption)
password = getPassword();
if (!password.isEmpty())
pipe.write(PKCS8::PEM_encode(*key, rng, password.toLocal8Bit().data()));
else
pipe.write(PKCS8::PEM_encode(*key));
keyData = &m_privateKey;
} else {
pipe.write(X509::PEM_encode(*key));
keyData = &m_publicKey;
}
pipe.end_msg();
keyData->resize(static_cast<int>(pipe.remaining(pipe.message_count() - 1)));
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;
QByteArray q;
switch (m_type) {
case Rsa: {
const QSharedPointer<RSA_PrivateKey> rsaKey = key.dynamicCast<RSA_PrivateKey>();
params << rsaKey->get_e() << rsaKey->get_n();
keyId = SshCapabilities::PubKeyRsa;
break;
}
case Dsa: {
const QSharedPointer<DSA_PrivateKey> dsaKey = key.dynamicCast<DSA_PrivateKey>();
params << dsaKey->group_p() << dsaKey->group_q() << dsaKey->group_g() << dsaKey->get_y();
keyId = SshCapabilities::PubKeyDss;
break;
}
case Ecdsa: {
const auto ecdsaKey = key.dynamicCast<ECDSA_PrivateKey>();
q = convertByteArray(EC2OSP(ecdsaKey->public_point(), PointGFp::UNCOMPRESSED));
keyId = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(
static_cast<int>(ecdsaKey->private_value().bytes()));
break;
}
}
QByteArray publicKeyBlob = AbstractSshPacket::encodeString(keyId);
foreach (const BigInt &b, params)
publicKeyBlob += AbstractSshPacket::encodeMpInt(b);
if (!q.isEmpty()) {
publicKeyBlob += AbstractSshPacket::encodeString(keyId.mid(11)); // Without "ecdsa-sha2-" prefix.
publicKeyBlob += AbstractSshPacket::encodeString(q);
}
publicKeyBlob = publicKeyBlob.toBase64();
const QByteArray id = "QtCreator/"
+ QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8();
m_publicKey = keyId + ' ' + publicKeyBlob + ' ' + id;
}
void SshKeyGenerator::generateOpenSslPrivateKeyString(const KeyPtr &key)
{
QList<BigInt> params;
const char *label = "";
switch (m_type) {
case Rsa: {
const QSharedPointer<RSA_PrivateKey> rsaKey
= key.dynamicCast<RSA_PrivateKey>();
params << rsaKey->get_n() << rsaKey->get_e() << rsaKey->get_d() << rsaKey->get_p()
<< rsaKey->get_q();
const BigInt dmp1 = rsaKey->get_d() % (rsaKey->get_p() - 1);
const BigInt dmq1 = rsaKey->get_d() % (rsaKey->get_q() - 1);
const BigInt iqmp = inverse_mod(rsaKey->get_q(), rsaKey->get_p());
params << dmp1 << dmq1 << iqmp;
label = "RSA PRIVATE KEY";
break;
}
case Dsa: {
const QSharedPointer<DSA_PrivateKey> dsaKey = key.dynamicCast<DSA_PrivateKey>();
params << dsaKey->group_p() << dsaKey->group_q() << dsaKey->group_g() << dsaKey->get_y()
<< dsaKey->get_x();
label = "DSA PRIVATE KEY";
break;
}
case Ecdsa:
params << key.dynamicCast<ECDSA_PrivateKey>()->private_value();
label = "EC PRIVATE KEY";
break;
}
DER_Encoder encoder;
encoder.start_cons(SEQUENCE).encode(size_t(0));
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

View file

@ -0,0 +1,75 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "ssh_global.h"
#include <QCoreApplication>
#include <QSharedPointer>
namespace Botan {
class Private_Key;
class RandomNumberGenerator;
}
namespace QSsh {
class QSSH_EXPORT SshKeyGenerator
{
Q_DECLARE_TR_FUNCTIONS(SshKeyGenerator)
public:
enum KeyType { Rsa, Dsa, Ecdsa };
enum PrivateKeyFormat { Pkcs8, OpenSsl, Mixed };
enum EncryptionMode { DoOfferEncryption, DoNotOfferEncryption }; // Only relevant for Pkcs8 format.
SshKeyGenerator();
bool generateKeys(KeyType type, PrivateKeyFormat format, int keySize,
EncryptionMode encryptionMode = DoOfferEncryption);
QString error() const { return m_error; }
QByteArray privateKey() const { return m_privateKey; }
QByteArray publicKey() const { return m_publicKey; }
KeyType type() const { return m_type; }
private:
typedef QSharedPointer<Botan::Private_Key> KeyPtr;
void generatePkcs8KeyStrings(const KeyPtr &key, Botan::RandomNumberGenerator &rng);
void generatePkcs8KeyString(const KeyPtr &key, bool privateKey,
Botan::RandomNumberGenerator &rng);
void generateOpenSslKeyStrings(const KeyPtr &key);
void generateOpenSslPrivateKeyString(const KeyPtr &key);
void generateOpenSslPublicKeyString(const KeyPtr &key);
QString getPassword() const;
QString m_error;
QByteArray m_publicKey;
QByteArray m_privateKey;
KeyType m_type;
EncryptionMode m_encryptionMode;
};
} // namespace QSsh

View file

@ -0,0 +1,59 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "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

View file

@ -0,0 +1,43 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <botan/botan.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

View file

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

View file

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

View file

@ -0,0 +1,381 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshoutgoingpacket_p.h"
#include "sshcapabilities_p.h"
#include "sshcryptofacility_p.h"
#include "sshlogging_p.h"
#include <QtEndian>
namespace QSsh {
namespace Internal {
SshOutgoingPacket::SshOutgoingPacket(const SshEncryptionFacility &encrypter,
const quint32 &seqNr) : m_encrypter(encrypter), m_seqNr(seqNr)
{
}
quint32 SshOutgoingPacket::cipherBlockSize() const
{
return qMax(m_encrypter.cipherBlockSize(), 4U);
}
quint32 SshOutgoingPacket::macLength() const
{
return m_encrypter.macLength();
}
QByteArray SshOutgoingPacket::generateKeyExchangeInitPacket()
{
const QByteArray &supportedkeyExchangeMethods
= encodeNameList(SshCapabilities::KeyExchangeMethods);
const QByteArray &supportedPublicKeyAlgorithms
= encodeNameList(SshCapabilities::PublicKeyAlgorithms);
const QByteArray &supportedEncryptionAlgorithms
= encodeNameList(SshCapabilities::EncryptionAlgorithms);
const QByteArray &supportedMacAlgorithms
= encodeNameList(SshCapabilities::MacAlgorithms);
const QByteArray &supportedCompressionAlgorithms
= encodeNameList(SshCapabilities::CompressionAlgorithms);
const QByteArray &supportedLanguages = encodeNameList(QList<QByteArray>());
init(SSH_MSG_KEXINIT);
m_data += m_encrypter.getRandomNumbers(16);
m_data.append(supportedkeyExchangeMethods);
m_data.append(supportedPublicKeyAlgorithms);
m_data.append(supportedEncryptionAlgorithms)
.append(supportedEncryptionAlgorithms);
m_data.append(supportedMacAlgorithms).append(supportedMacAlgorithms);
m_data.append(supportedCompressionAlgorithms)
.append(supportedCompressionAlgorithms);
m_data.append(supportedLanguages).append(supportedLanguages);
appendBool(false); // No guessed packet.
m_data.append(QByteArray(4, 0)); // Reserved.
QByteArray payload = m_data.mid(PayloadOffset);
finalize();
return payload;
}
void SshOutgoingPacket::generateKeyDhInitPacket(const Botan::BigInt &e)
{
init(SSH_MSG_KEXDH_INIT).appendMpInt(e).finalize();
}
void SshOutgoingPacket::generateKeyEcdhInitPacket(const QByteArray &clientQ)
{
init(SSH_MSG_KEX_ECDH_INIT).appendString(clientQ).finalize();
}
void SshOutgoingPacket::generateNewKeysPacket()
{
init(SSH_MSG_NEWKEYS).finalize();
}
void SshOutgoingPacket::generateUserAuthServiceRequestPacket()
{
generateServiceRequest("ssh-userauth");
}
void SshOutgoingPacket::generateServiceRequest(const QByteArray &service)
{
init(SSH_MSG_SERVICE_REQUEST).appendString(service).finalize();
}
void SshOutgoingPacket::generateUserAuthByPasswordRequestPacket(const QByteArray &user,
const QByteArray &service, const QByteArray &pwd)
{
init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service);
if (pwd.isEmpty())
appendString("none"); // RFC 4252, 5.2
else
appendString("password").appendBool(false).appendString(pwd);
finalize();
}
void SshOutgoingPacket::generateUserAuthByPublicKeyRequestPacket(const QByteArray &user,
const QByteArray &service)
{
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::generateUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
const QByteArray &service)
{
// RFC 4256, 3.1
init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service)
.appendString("keyboard-interactive")
.appendString(QByteArray()) // Language tag. Deprecated and should be empty
.appendString(QByteArray()) // Submethods.
.finalize();
}
void SshOutgoingPacket::generateUserAuthInfoResponsePacket(const QStringList &responses)
{
// RFC 4256, 3.4
init(SSH_MSG_USERAUTH_INFO_RESPONSE).appendInt(responses.count());
foreach (const QString &response, responses)
appendString(response.toUtf8());
finalize();
}
void SshOutgoingPacket::generateRequestFailurePacket()
{
init(SSH_MSG_REQUEST_FAILURE).finalize();
}
void SshOutgoingPacket::generateIgnorePacket()
{
init(SSH_MSG_IGNORE).finalize();
}
void SshOutgoingPacket::generateInvalidMessagePacket()
{
init(SSH_MSG_INVALID).finalize();
}
void SshOutgoingPacket::generateSessionPacket(quint32 channelId,
quint32 windowSize, quint32 maxPacketSize)
{
init(SSH_MSG_CHANNEL_OPEN).appendString("session").appendInt(channelId)
.appendInt(windowSize).appendInt(maxPacketSize).finalize();
}
void SshOutgoingPacket::generateDirectTcpIpPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize, const QByteArray &remoteHost, quint32 remotePort,
const QByteArray &localIpAddress, quint32 localPort)
{
init(SSH_MSG_CHANNEL_OPEN).appendString("direct-tcpip").appendInt(channelId)
.appendInt(windowSize).appendInt(maxPacketSize).appendString(remoteHost)
.appendInt(remotePort).appendString(localIpAddress).appendInt(localPort).finalize();
}
void SshOutgoingPacket::generateTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort)
{
init(SSH_MSG_GLOBAL_REQUEST).appendString("tcpip-forward").appendBool(true)
.appendString(bindAddress).appendInt(bindPort).finalize();
}
void SshOutgoingPacket::generateCancelTcpIpForwardPacket(const QByteArray &bindAddress,
quint32 bindPort)
{
init(SSH_MSG_GLOBAL_REQUEST).appendString("cancel-tcpip-forward").appendBool(true)
.appendString(bindAddress).appendInt(bindPort).finalize();
}
void SshOutgoingPacket::generateEnvPacket(quint32 remoteChannel,
const QByteArray &var, const QByteArray &value)
{
init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("env")
.appendBool(false).appendString(var).appendString(value).finalize();
}
void SshOutgoingPacket::generatePtyRequestPacket(quint32 remoteChannel,
const SshPseudoTerminal &terminal)
{
init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel)
.appendString("pty-req").appendBool(false)
.appendString(terminal.termType).appendInt(terminal.columnCount)
.appendInt(terminal.rowCount).appendInt(0).appendInt(0);
QByteArray modeString;
for (SshPseudoTerminal::ModeMap::ConstIterator it = terminal.modes.constBegin();
it != terminal.modes.constEnd(); ++it) {
modeString += char(it.key());
modeString += encodeInt(it.value());
}
modeString += char(0); // TTY_OP_END
appendString(modeString).finalize();
}
void SshOutgoingPacket::generateExecPacket(quint32 remoteChannel,
const QByteArray &command)
{
init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("exec")
.appendBool(true).appendString(command).finalize();
}
void SshOutgoingPacket::generateShellPacket(quint32 remoteChannel)
{
init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("shell")
.appendBool(true).finalize();
}
void SshOutgoingPacket::generateSftpPacket(quint32 remoteChannel)
{
init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel)
.appendString("subsystem").appendBool(true).appendString("sftp")
.finalize();
}
void SshOutgoingPacket::generateWindowAdjustPacket(quint32 remoteChannel,
quint32 bytesToAdd)
{
init(SSH_MSG_CHANNEL_WINDOW_ADJUST).appendInt(remoteChannel)
.appendInt(bytesToAdd).finalize();
}
void SshOutgoingPacket::generateChannelDataPacket(quint32 remoteChannel,
const QByteArray &data)
{
init(SSH_MSG_CHANNEL_DATA).appendInt(remoteChannel).appendString(data)
.finalize();
}
void SshOutgoingPacket::generateChannelSignalPacket(quint32 remoteChannel,
const QByteArray &signalName)
{
init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel)
.appendString("signal").appendBool(false).appendString(signalName)
.finalize();
}
void SshOutgoingPacket::generateChannelEofPacket(quint32 remoteChannel)
{
init(SSH_MSG_CHANNEL_EOF).appendInt(remoteChannel).finalize();
}
void SshOutgoingPacket::generateChannelClosePacket(quint32 remoteChannel)
{
init(SSH_MSG_CHANNEL_CLOSE).appendInt(remoteChannel).finalize();
}
void SshOutgoingPacket::generateChannelOpenConfirmationPacket(quint32 remoteChannel,
quint32 localChannel,
quint32 localWindowSize,
quint32 maxPacketSize)
{
init(SSH_MSG_CHANNEL_OPEN_CONFIRMATION).appendInt(remoteChannel).appendInt(localChannel)
.appendInt(localWindowSize).appendInt(maxPacketSize).finalize();
}
void SshOutgoingPacket::generateChannelOpenFailurePacket(quint32 remoteChannel, quint32 reason,
const QByteArray &reasonString)
{
init(SSH_MSG_CHANNEL_OPEN_FAILURE).appendInt(remoteChannel).appendInt(reason)
.appendString(reasonString).appendString(QByteArray()).finalize();
}
void SshOutgoingPacket::generateDisconnectPacket(SshErrorCode reason,
const QByteArray &reasonString)
{
init(SSH_MSG_DISCONNECT).appendInt(reason).appendString(reasonString)
.appendString(QByteArray()).finalize();
}
void SshOutgoingPacket::generateMsgUnimplementedPacket(quint32 serverSeqNr)
{
init(SSH_MSG_UNIMPLEMENTED).appendInt(serverSeqNr).finalize();
}
SshOutgoingPacket &SshOutgoingPacket::appendInt(quint32 val)
{
m_data.append(encodeInt(val));
return *this;
}
SshOutgoingPacket &SshOutgoingPacket::appendMpInt(const Botan::BigInt &number)
{
m_data.append(encodeMpInt(number));
return *this;
}
SshOutgoingPacket &SshOutgoingPacket::appendBool(bool b)
{
m_data += static_cast<char>(b);
return *this;
}
SshOutgoingPacket &SshOutgoingPacket::appendString(const QByteArray &string)
{
m_data.append(encodeString(string));
return *this;
}
SshOutgoingPacket &SshOutgoingPacket::init(SshPacketType type)
{
m_data.resize(TypeOffset + 1);
m_data[TypeOffset] = type;
return *this;
}
SshOutgoingPacket &SshOutgoingPacket::setPadding()
{
m_data += m_encrypter.getRandomNumbers(MinPaddingLength);
int padLength = MinPaddingLength;
const int divisor = sizeDivisor();
const int mod = m_data.size() % divisor;
padLength += divisor - mod;
m_data += m_encrypter.getRandomNumbers(padLength - MinPaddingLength);
m_data[PaddingLengthOffset] = padLength;
return *this;
}
SshOutgoingPacket &SshOutgoingPacket::encrypt()
{
const QByteArray &mac
= generateMac(m_encrypter, m_seqNr);
m_encrypter.encrypt(m_data);
m_data += mac;
return *this;
}
void SshOutgoingPacket::finalize()
{
setPadding();
setLengthField(m_data);
m_length = m_data.size() - 4;
qCDebug(sshLog, "Encrypting packet of type %u", m_data.at(TypeOffset));
encrypt();
qCDebug(sshLog, "Sending packet of size %d", rawData().count());
Q_ASSERT(isComplete());
}
int SshOutgoingPacket::sizeDivisor() const
{
return qMax(cipherBlockSize(), 8U);
}
QByteArray SshOutgoingPacket::encodeNameList(const QList<QByteArray> &list)
{
QByteArray data;
data.resize(4);
for (int i = 0; i < list.count(); ++i) {
if (i > 0)
data.append(',');
data.append(list.at(i));
}
AbstractSshPacket::setLengthField(data);
return data;
}
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,113 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "sshpacket_p.h"
#include "sshpseudoterminal.h"
#include <QStringList>
namespace QSsh {
namespace Internal {
class SshEncryptionFacility;
class SshOutgoingPacket : public AbstractSshPacket
{
public:
SshOutgoingPacket(const SshEncryptionFacility &encrypter,
const quint32 &seqNr);
QByteArray generateKeyExchangeInitPacket(); // Returns payload.
void generateKeyDhInitPacket(const Botan::BigInt &e);
void generateKeyEcdhInitPacket(const QByteArray &clientQ);
void generateNewKeysPacket();
void generateDisconnectPacket(SshErrorCode reason,
const QByteArray &reasonString);
void generateMsgUnimplementedPacket(quint32 serverSeqNr);
void generateUserAuthServiceRequestPacket();
void generateUserAuthByPasswordRequestPacket(const QByteArray &user,
const QByteArray &service, const QByteArray &pwd);
void generateUserAuthByPublicKeyRequestPacket(const QByteArray &user,
const QByteArray &service);
void generateUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
const QByteArray &service);
void generateUserAuthInfoResponsePacket(const QStringList &responses);
void generateRequestFailurePacket();
void generateIgnorePacket();
void generateInvalidMessagePacket();
void generateSessionPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize);
void generateDirectTcpIpPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize, const QByteArray &remoteHost, quint32 remotePort,
const QByteArray &localIpAddress, quint32 localPort);
void generateTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort);
void generateCancelTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort);
void generateEnvPacket(quint32 remoteChannel, const QByteArray &var,
const QByteArray &value);
void generatePtyRequestPacket(quint32 remoteChannel,
const SshPseudoTerminal &terminal);
void generateExecPacket(quint32 remoteChannel, const QByteArray &command);
void generateShellPacket(quint32 remoteChannel);
void generateSftpPacket(quint32 remoteChannel);
void generateWindowAdjustPacket(quint32 remoteChannel, quint32 bytesToAdd);
void generateChannelDataPacket(quint32 remoteChannel,
const QByteArray &data);
void generateChannelSignalPacket(quint32 remoteChannel,
const QByteArray &signalName);
void generateChannelEofPacket(quint32 remoteChannel);
void generateChannelClosePacket(quint32 remoteChannel);
void generateChannelOpenConfirmationPacket(quint32 remoteChannel, quint32 localChannel,
quint32 localWindowSize, quint32 maxPackeSize);
void generateChannelOpenFailurePacket(quint32 remoteChannel, quint32 reason,
const QByteArray &reasonString);
private:
virtual quint32 cipherBlockSize() const;
virtual quint32 macLength() const;
static QByteArray encodeNameList(const QList<QByteArray> &list);
void generateServiceRequest(const QByteArray &service);
SshOutgoingPacket &init(SshPacketType type);
SshOutgoingPacket &setPadding();
SshOutgoingPacket &encrypt();
void finalize();
SshOutgoingPacket &appendInt(quint32 val);
SshOutgoingPacket &appendString(const QByteArray &string);
SshOutgoingPacket &appendMpInt(const Botan::BigInt &number);
SshOutgoingPacket &appendBool(bool b);
int sizeDivisor() const;
const SshEncryptionFacility &m_encrypter;
const quint32 &m_seqNr;
};
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,153 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshpacket_p.h"
#include "sshcapabilities_p.h"
#include "sshcryptofacility_p.h"
#include "sshexception_p.h"
#include "sshlogging_p.h"
#include "sshpacketparser_p.h"
#include <QDebug>
#include <cctype>
namespace QSsh {
namespace Internal {
const quint32 AbstractSshPacket::PaddingLengthOffset = 4;
const quint32 AbstractSshPacket::PayloadOffset = PaddingLengthOffset + 1;
const quint32 AbstractSshPacket::TypeOffset = PayloadOffset;
const quint32 AbstractSshPacket::MinPaddingLength = 4;
static void printByteArray(const QByteArray &data)
{
qCDebug(sshLog, "%s", data.toHex().constData());
}
AbstractSshPacket::AbstractSshPacket() : m_length(0) { }
AbstractSshPacket::~AbstractSshPacket() {}
bool AbstractSshPacket::isComplete() const
{
if (currentDataSize() < minPacketSize())
return false;
return 4 + length() + macLength() == currentDataSize();
}
void AbstractSshPacket::clear()
{
m_data.clear();
m_length = 0;
}
SshPacketType AbstractSshPacket::type() const
{
Q_ASSERT(isComplete());
return static_cast<SshPacketType>(m_data.at(TypeOffset));
}
QByteArray AbstractSshPacket::payLoad() const
{
return QByteArray(m_data.constData() + PayloadOffset,
length() - paddingLength() - 1);
}
void AbstractSshPacket::printRawBytes() const
{
printByteArray(m_data);
}
QByteArray AbstractSshPacket::encodeString(const QByteArray &string)
{
QByteArray data;
data.resize(4);
data += string;
setLengthField(data);
return data;
}
QByteArray AbstractSshPacket::encodeMpInt(const Botan::BigInt &number)
{
if (number.is_zero())
return QByteArray(4, 0);
int stringLength = static_cast<int>(number.bytes());
const bool positiveAndMsbSet = number.sign() == Botan::BigInt::Positive
&& (number.byte_at(stringLength - 1) & 0x80);
if (positiveAndMsbSet)
++stringLength;
QByteArray data;
data.resize(4 + stringLength);
int pos = 4;
if (positiveAndMsbSet)
data[pos++] = '\0';
number.binary_encode(reinterpret_cast<Botan::byte *>(data.data()) + pos);
setLengthField(data);
return data;
}
int AbstractSshPacket::paddingLength() const
{
return m_data[PaddingLengthOffset];
}
quint32 AbstractSshPacket::length() const
{
//Q_ASSERT(currentDataSize() >= minPacketSize());
if (m_length == 0)
calculateLength();
return m_length;
}
void AbstractSshPacket::calculateLength() const
{
m_length = SshPacketParser::asUint32(m_data, static_cast<quint32>(0));
}
QByteArray AbstractSshPacket::generateMac(const SshAbstractCryptoFacility &crypt,
quint32 seqNr) const
{
const quint32 seqNrBe = qToBigEndian(seqNr);
QByteArray data(reinterpret_cast<const char *>(&seqNrBe), 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

View file

@ -0,0 +1,147 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "sshexception_p.h"
#include <QtEndian>
#include <QByteArray>
#include <QList>
namespace Botan { class BigInt; }
namespace QSsh {
namespace Internal {
enum SshPacketType {
SSH_MSG_DISCONNECT = 1,
SSH_MSG_IGNORE = 2,
SSH_MSG_UNIMPLEMENTED = 3,
SSH_MSG_DEBUG = 4,
SSH_MSG_SERVICE_REQUEST = 5,
SSH_MSG_SERVICE_ACCEPT = 6,
SSH_MSG_KEXINIT = 20,
SSH_MSG_NEWKEYS = 21,
SSH_MSG_KEXDH_INIT = 30,
SSH_MSG_KEX_ECDH_INIT = 30,
SSH_MSG_KEXDH_REPLY = 31,
SSH_MSG_KEX_ECDH_REPLY = 31,
SSH_MSG_USERAUTH_REQUEST = 50,
SSH_MSG_USERAUTH_FAILURE = 51,
SSH_MSG_USERAUTH_SUCCESS = 52,
SSH_MSG_USERAUTH_BANNER = 53,
SSH_MSG_USERAUTH_PK_OK = 60,
SSH_MSG_USERAUTH_PASSWD_CHANGEREQ = 60,
SSH_MSG_USERAUTH_INFO_REQUEST = 60,
SSH_MSG_USERAUTH_INFO_RESPONSE = 61,
SSH_MSG_GLOBAL_REQUEST = 80,
SSH_MSG_REQUEST_SUCCESS = 81,
SSH_MSG_REQUEST_FAILURE = 82,
// TODO: We currently take no precautions against sending these messages
// during a key re-exchange, which is not allowed.
SSH_MSG_CHANNEL_OPEN = 90,
SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91,
SSH_MSG_CHANNEL_OPEN_FAILURE = 92,
SSH_MSG_CHANNEL_WINDOW_ADJUST = 93,
SSH_MSG_CHANNEL_DATA = 94,
SSH_MSG_CHANNEL_EXTENDED_DATA = 95,
SSH_MSG_CHANNEL_EOF = 96,
SSH_MSG_CHANNEL_CLOSE = 97,
SSH_MSG_CHANNEL_REQUEST = 98,
SSH_MSG_CHANNEL_SUCCESS = 99,
SSH_MSG_CHANNEL_FAILURE = 100,
// Not completely safe, since the server may actually understand this
// message type as an extension. Switch to a different value in that case
// (between 128 and 191).
SSH_MSG_INVALID = 128
};
enum SshOpenFailureType {
SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1,
SSH_OPEN_CONNECT_FAILED = 2,
SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3,
SSH_OPEN_RESOURCE_SHORTAGE = 4
};
enum SshExtendedDataType { SSH_EXTENDED_DATA_STDERR = 1 };
class SshAbstractCryptoFacility;
class AbstractSshPacket
{
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

View file

@ -0,0 +1,149 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "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

View file

@ -0,0 +1,74 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <botan/botan.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

View file

@ -0,0 +1,110 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "ssh_global.h"
#include <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

View file

@ -0,0 +1,380 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshremoteprocess.h"
#include "sshremoteprocess_p.h"
#include "ssh_global.h"
#include "sshincomingpacket_p.h"
#include "sshlogging_p.h"
#include "sshsendfacility_p.h"
#include <botan/botan.h>
#include <QTimer>
#include <cstring>
#include <cstdlib>
/*!
\class QSsh::SshRemoteProcess
\brief The SshRemoteProcess 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()
{
QSSH_ASSERT(d->channelState() != Internal::AbstractSshChannel::SessionEstablished);
close();
delete d;
}
bool SshRemoteProcess::atEnd() const
{
return QIODevice::atEnd() && d->data().isEmpty();
}
qint64 SshRemoteProcess::bytesAvailable() const
{
return QIODevice::bytesAvailable() + d->data().count();
}
bool SshRemoteProcess::canReadLine() const
{
return QIODevice::canReadLine() || d->data().contains('\n');
}
QByteArray SshRemoteProcess::readAllStandardOutput()
{
return readAllFromChannel(QProcess::StandardOutput);
}
QByteArray SshRemoteProcess::readAllStandardError()
{
return readAllFromChannel(QProcess::StandardError);
}
QByteArray SshRemoteProcess::readAllFromChannel(QProcess::ProcessChannel channel)
{
const QProcess::ProcessChannel currentReadChannel = readChannel();
setReadChannel(channel);
const QByteArray &data = readAll();
setReadChannel(currentReadChannel);
return data;
}
void SshRemoteProcess::close()
{
d->closeChannel();
QIODevice::close();
}
qint64 SshRemoteProcess::readData(char *data, qint64 maxlen)
{
const qint64 bytesRead = qMin(qint64(d->data().count()), maxlen);
memcpy(data, d->data().constData(), bytesRead);
d->data().remove(0, bytesRead);
return bytesRead;
}
qint64 SshRemoteProcess::writeData(const char *data, qint64 len)
{
if (isRunning()) {
d->sendData(QByteArray(data, len));
return len;
}
return 0;
}
QProcess::ProcessChannel SshRemoteProcess::readChannel() const
{
return d->m_readChannel;
}
void SshRemoteProcess::setReadChannel(QProcess::ProcessChannel channel)
{
d->m_readChannel = channel;
}
void SshRemoteProcess::init()
{
connect(d, &Internal::SshRemoteProcessPrivate::started,
this, &SshRemoteProcess::started, Qt::QueuedConnection);
connect(d, &Internal::SshRemoteProcessPrivate::readyReadStandardOutput,
this, &SshRemoteProcess::readyReadStandardOutput, Qt::QueuedConnection);
connect(d, &Internal::SshRemoteProcessPrivate::readyRead,
this, &SshRemoteProcess::readyRead, Qt::QueuedConnection);
connect(d, &Internal::SshRemoteProcessPrivate::readyReadStandardError,
this, &SshRemoteProcess::readyReadStandardError, Qt::QueuedConnection);
connect(d, &Internal::SshRemoteProcessPrivate::closed,
this, &SshRemoteProcess::closed, Qt::QueuedConnection);
connect(d, &Internal::SshRemoteProcessPrivate::eof,
this, &SshRemoteProcess::readChannelFinished, Qt::QueuedConnection);
}
void SshRemoteProcess::addToEnvironment(const QByteArray &var, const QByteArray &value)
{
if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive)
d->m_env << qMakePair(var, value); // Cached locally and sent on start()
}
void SshRemoteProcess::clearEnvironment()
{
d->m_env.clear();
}
void SshRemoteProcess::requestTerminal(const SshPseudoTerminal &terminal)
{
QSSH_ASSERT_AND_RETURN(d->channelState() == Internal::SshRemoteProcessPrivate::Inactive);
d->m_useTerminal = true;
d->m_terminal = terminal;
}
void SshRemoteProcess::start()
{
if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive) {
qCDebug(Internal::sshLog, "process start requested, channel id = %u", d->localChannelId());
QIODevice::open(QIODevice::ReadWrite);
d->requestSessionStart();
}
}
void SshRemoteProcess::sendSignal(Signal signal)
{
try {
if (isRunning()) {
const char *signalString = 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 (const std::exception &e) {
setErrorString(QString::fromLatin1(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)
{
qCDebug(sshLog, "channel: old state = %d,new state = %d", m_procState, newState);
m_procState = newState;
if (newState == StartFailed) {
emit closed(SshRemoteProcess::FailedToStart);
} else if (newState == Running) {
m_wasRunning = true;
emit started();
}
}
QByteArray &SshRemoteProcessPrivate::data()
{
return m_readChannel == QProcess::StandardOutput ? m_stdout : m_stderr;
}
void SshRemoteProcessPrivate::closeHook()
{
if (m_wasRunning) {
if (m_signal != SshRemoteProcess::NoSignal)
emit closed(SshRemoteProcess::CrashExit);
else
emit closed(SshRemoteProcess::NormalExit);
}
}
void SshRemoteProcessPrivate::handleOpenSuccessInternal()
{
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) {
qCWarning(sshLog, "Unknown extended data type %u", type);
} else {
m_stderr += data;
emit readyReadStandardError();
if (m_readChannel == QProcess::StandardError)
emit readyRead();
}
}
void SshRemoteProcessPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
{
qCDebug(sshLog, "Process exiting with exit code %d", exitStatus.exitStatus);
m_exitCode = exitStatus.exitStatus;
m_procState = Exited;
}
void SshRemoteProcessPrivate::handleExitSignal(const SshChannelExitSignal &signal)
{
qCDebug(sshLog, "Exit due to signal %s", signal.signal.data());
for (size_t i = 0; i < sizeof signalMap/sizeof *signalMap; ++i) {
if (signalMap[i].signalString == signal.signal) {
m_signal = signalMap[i].signalEnum;
m_procState = Exited;
m_proc->setErrorString(tr("Process killed by signal"));
return;
}
}
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, "Invalid signal",
tr("Server sent invalid signal \"%1\"").arg(QString::fromUtf8(signal.signal)));
}
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,121 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "ssh_global.h"
#include <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, CrashExit, NormalExit };
enum Signal {
AbrtSignal, AlrmSignal, FpeSignal, HupSignal, IllSignal, IntSignal, KillSignal, PipeSignal,
QuitSignal, SegvSignal, TermSignal, Usr1Signal, Usr2Signal, NoSignal
};
~SshRemoteProcess();
// QIODevice stuff
bool atEnd() const;
qint64 bytesAvailable() const;
bool canReadLine() const;
void close();
bool isSequential() const { return true; }
QProcess::ProcessChannel readChannel() const;
void setReadChannel(QProcess::ProcessChannel channel);
/*
* Note that this is of limited value in practice, because servers are
* usually configured to ignore such requests for security reasons.
*/
void addToEnvironment(const QByteArray &var, const QByteArray &value);
void clearEnvironment();
void requestTerminal(const SshPseudoTerminal &terminal);
void 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

View file

@ -0,0 +1,103 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "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
};
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 handleChannelSuccess();
virtual void handleChannelFailure();
virtual void handleOpenSuccessInternal();
virtual void handleOpenFailureInternal(const QString &reason);
virtual void handleChannelDataInternal(const QByteArray &data);
virtual void handleChannelExtendedDataInternal(quint32 type,
const QByteArray &data);
virtual void handleExitStatus(const SshChannelExitStatus &exitStatus);
virtual void handleExitSignal(const SshChannelExitSignal &signal);
virtual void closeHook();
void init();
void setProcState(ProcessState newState);
QByteArray &data();
QProcess::ProcessChannel m_readChannel;
ProcessState m_procState;
bool m_wasRunning;
int m_signal;
int m_exitCode;
const QByteArray m_command;
const bool m_isShell;
typedef QPair<QByteArray, QByteArray> EnvVar;
QList<EnvVar> m_env;
bool m_useTerminal;
SshPseudoTerminal m_terminal;
QByteArray m_stdout;
QByteArray m_stderr;
SshRemoteProcess *m_proc;
};
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,286 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshremoteprocessrunner.h"
#include "sshconnectionmanager.h"
#include "sshpseudoterminal.h"
/*!
\class QSsh::SshRemoteProcessRunner
\brief The SshRemoteProcessRunner class is a 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 *m_connection;
bool m_runInTerminal;
SshPseudoTerminal m_terminal;
QByteArray m_command;
QSsh::SshError m_lastConnectionError;
QString m_lastConnectionErrorString;
SshRemoteProcess::ExitStatus m_exitStatus;
SshRemoteProcess::Signal m_exitSignal;
QByteArray m_stdout;
QByteArray m_stderr;
int m_exitCode;
QString m_processErrorString;
State m_state;
};
} // namespace Internal
using namespace Internal;
SshRemoteProcessRunner::SshRemoteProcessRunner(QObject *parent)
: QObject(parent), d(new SshRemoteProcessRunnerPrivate)
{
}
SshRemoteProcessRunner::~SshRemoteProcessRunner()
{
disconnect();
setState(Inactive);
delete d;
}
void SshRemoteProcessRunner::run(const QByteArray &command,
const SshConnectionParameters &sshParams)
{
QSSH_ASSERT_AND_RETURN(d->m_state == Inactive);
d->m_runInTerminal = false;
runInternal(command, sshParams);
}
void SshRemoteProcessRunner::runInTerminal(const QByteArray &command,
const SshPseudoTerminal &terminal, const SshConnectionParameters &sshParams)
{
d->m_terminal = terminal;
d->m_runInTerminal = true;
runInternal(command, sshParams);
}
void SshRemoteProcessRunner::runInternal(const QByteArray &command,
const SshConnectionParameters &sshParams)
{
setState(Connecting);
d->m_lastConnectionError = SshNoError;
d->m_lastConnectionErrorString.clear();
d->m_processErrorString.clear();
d->m_exitSignal = SshRemoteProcess::NoSignal;
d->m_exitCode = -1;
d->m_command = command;
d->m_connection = QSsh::acquireConnection(sshParams);
connect(d->m_connection, &SshConnection::error,
this, &SshRemoteProcessRunner::handleConnectionError);
connect(d->m_connection, &SshConnection::disconnected,
this, &SshRemoteProcessRunner::handleDisconnected);
if (d->m_connection->state() == SshConnection::Connected) {
handleConnected();
} else {
connect(d->m_connection, &SshConnection::connected, this, &SshRemoteProcessRunner::handleConnected);
if (d->m_connection->state() == SshConnection::Unconnected)
d->m_connection->connectToHost();
}
}
void SshRemoteProcessRunner::handleConnected()
{
QSSH_ASSERT_AND_RETURN(d->m_state == Connecting);
setState(Connected);
d->m_process = d->m_connection->createRemoteProcess(d->m_command);
connect(d->m_process.data(), &SshRemoteProcess::started,
this, &SshRemoteProcessRunner::handleProcessStarted);
connect(d->m_process.data(), &SshRemoteProcess::closed,
this, &SshRemoteProcessRunner::handleProcessFinished);
connect(d->m_process.data(), &SshRemoteProcess::readyReadStandardOutput,
this, &SshRemoteProcessRunner::handleStdout);
connect(d->m_process.data(), &SshRemoteProcess::readyReadStandardError,
this, &SshRemoteProcessRunner::handleStderr);
if (d->m_runInTerminal)
d->m_process->requestTerminal(d->m_terminal);
d->m_process->start();
}
void SshRemoteProcessRunner::handleConnectionError(QSsh::SshError error)
{
d->m_lastConnectionError = error;
d->m_lastConnectionErrorString = d->m_connection->errorString();
handleDisconnected();
emit connectionError();
}
void SshRemoteProcessRunner::handleDisconnected()
{
QSSH_ASSERT_AND_RETURN(d->m_state == Connecting || d->m_state == Connected
|| d->m_state == ProcessRunning);
setState(Inactive);
}
void SshRemoteProcessRunner::handleProcessStarted()
{
QSSH_ASSERT_AND_RETURN(d->m_state == Connected);
setState(ProcessRunning);
emit processStarted();
}
void SshRemoteProcessRunner::handleProcessFinished(int exitStatus)
{
d->m_exitStatus = static_cast<SshRemoteProcess::ExitStatus>(exitStatus);
switch (d->m_exitStatus) {
case SshRemoteProcess::FailedToStart:
QSSH_ASSERT_AND_RETURN(d->m_state == Connected);
break;
case SshRemoteProcess::CrashExit:
QSSH_ASSERT_AND_RETURN(d->m_state == ProcessRunning);
d->m_exitSignal = d->m_process->exitSignal();
break;
case SshRemoteProcess::NormalExit:
QSSH_ASSERT_AND_RETURN(d->m_state == ProcessRunning);
d->m_exitCode = d->m_process->exitCode();
break;
default:
Q_ASSERT_X(false, Q_FUNC_INFO, "Impossible exit status.");
}
d->m_processErrorString = d->m_process->errorString();
setState(Inactive);
emit processClosed(exitStatus);
}
void SshRemoteProcessRunner::handleStdout()
{
d->m_stdout += d->m_process->readAllStandardOutput();
emit readyReadStandardOutput();
}
void SshRemoteProcessRunner::handleStderr()
{
d->m_stderr += d->m_process->readAllStandardError();
emit readyReadStandardError();
}
void SshRemoteProcessRunner::setState(int newState)
{
if (d->m_state == newState)
return;
d->m_state = static_cast<State>(newState);
if (d->m_state == Inactive) {
if (d->m_process) {
disconnect(d->m_process.data(), 0, this, 0);
d->m_process->close();
d->m_process.clear();
}
if (d->m_connection) {
disconnect(d->m_connection, 0, this, 0);
QSsh::releaseConnection(d->m_connection);
d->m_connection = 0;
}
}
}
QByteArray SshRemoteProcessRunner::command() const { return d->m_command; }
SshError SshRemoteProcessRunner::lastConnectionError() const { return d->m_lastConnectionError; }
QString SshRemoteProcessRunner::lastConnectionErrorString() const {
return d->m_lastConnectionErrorString;
}
bool SshRemoteProcessRunner::isProcessRunning() const
{
return d->m_process && d->m_process->isRunning();
}
SshRemoteProcess::ExitStatus SshRemoteProcessRunner::processExitStatus() const
{
QSSH_ASSERT(!isProcessRunning());
return d->m_exitStatus;
}
SshRemoteProcess::Signal SshRemoteProcessRunner::processExitSignal() const
{
QSSH_ASSERT(processExitStatus() == SshRemoteProcess::CrashExit);
return d->m_exitSignal;
}
int SshRemoteProcessRunner::processExitCode() const
{
QSSH_ASSERT(processExitStatus() == SshRemoteProcess::NormalExit);
return d->m_exitCode;
}
QString SshRemoteProcessRunner::processErrorString() const
{
return d->m_processErrorString;
}
QByteArray SshRemoteProcessRunner::readAllStandardOutput()
{
const QByteArray data = d->m_stdout;
d->m_stdout.clear();
return data;
}
QByteArray SshRemoteProcessRunner::readAllStandardError()
{
const QByteArray data = d->m_stderr;
d->m_stderr.clear();
return data;
}
void SshRemoteProcessRunner::writeDataToProcess(const QByteArray &data)
{
QSSH_ASSERT(isProcessRunning());
d->m_process->write(data);
}
void SshRemoteProcessRunner::sendSignalToProcess(SshRemoteProcess::Signal signal)
{
QSSH_ASSERT(isProcessRunning());
d->m_process->sendSignal(signal);
}
void SshRemoteProcessRunner::cancel()
{
setState(Inactive);
}
} // namespace QSsh

View file

@ -0,0 +1,82 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "sshconnection.h"
#include "sshremoteprocess.h"
namespace QSsh {
namespace Internal { class SshRemoteProcessRunnerPrivate; }
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;
QByteArray readAllStandardOutput();
QByteArray readAllStandardError();
signals:
void connectionError();
void processStarted();
void readyReadStandardOutput();
void readyReadStandardError();
void processClosed(int exitStatus); // values are of type SshRemoteProcess::ExitStatus
private:
void handleConnected();
void handleConnectionError(QSsh::SshError error);
void handleDisconnected();
void handleProcessStarted();
void handleProcessFinished(int exitStatus);
void handleStdout();
void handleStderr();
void runInternal(const QByteArray &command, const QSsh::SshConnectionParameters &sshParams);
void setState(int newState);
Internal::SshRemoteProcessRunnerPrivate * const d;
};
} // namespace QSsh

View file

@ -0,0 +1,269 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "sshsendfacility_p.h"
#include "sshkeyexchange_p.h"
#include "sshlogging_p.h"
#include "sshoutgoingpacket_p.h"
#include <QTcpSocket>
namespace QSsh {
namespace Internal {
SshSendFacility::SshSendFacility(QTcpSocket *socket)
: m_clientSeqNr(0), m_socket(socket),
m_outgoingPacket(m_encrypter, m_clientSeqNr)
{
}
void SshSendFacility::sendPacket()
{
qCDebug(sshLog, "Sending packet, client seq nr is %u", m_clientSeqNr);
if (m_socket->isValid()
&& m_socket->state() == QAbstractSocket::ConnectedState) {
m_socket->write(m_outgoingPacket.rawData());
++m_clientSeqNr;
}
}
void SshSendFacility::reset()
{
m_clientSeqNr = 0;
m_encrypter.clearKeys();
}
void SshSendFacility::recreateKeys(const SshKeyExchange &keyExchange)
{
m_encrypter.recreateKeys(keyExchange);
}
void SshSendFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
{
m_encrypter.createAuthenticationKey(privKeyFileContents);
}
QByteArray SshSendFacility::sendKeyExchangeInitPacket()
{
const QByteArray &payLoad = m_outgoingPacket.generateKeyExchangeInitPacket();
sendPacket();
return payLoad;
}
void SshSendFacility::sendKeyDhInitPacket(const Botan::BigInt &e)
{
m_outgoingPacket.generateKeyDhInitPacket(e);
sendPacket();
}
void SshSendFacility::sendKeyEcdhInitPacket(const QByteArray &clientQ)
{
m_outgoingPacket.generateKeyEcdhInitPacket(clientQ);
sendPacket();
}
void SshSendFacility::sendNewKeysPacket()
{
m_outgoingPacket.generateNewKeysPacket();
sendPacket();
}
void SshSendFacility::sendDisconnectPacket(SshErrorCode reason,
const QByteArray &reasonString)
{
m_outgoingPacket.generateDisconnectPacket(reason, reasonString);
sendPacket();
}
void SshSendFacility::sendMsgUnimplementedPacket(quint32 serverSeqNr)
{
m_outgoingPacket.generateMsgUnimplementedPacket(serverSeqNr);
sendPacket();
}
void SshSendFacility::sendUserAuthServiceRequestPacket()
{
m_outgoingPacket.generateUserAuthServiceRequestPacket();
sendPacket();
}
void SshSendFacility::sendUserAuthByPasswordRequestPacket(const QByteArray &user,
const QByteArray &service, const QByteArray &pwd)
{
m_outgoingPacket.generateUserAuthByPasswordRequestPacket(user, service, pwd);
sendPacket();
}
void SshSendFacility::sendUserAuthByPublicKeyRequestPacket(const QByteArray &user,
const QByteArray &service)
{
m_outgoingPacket.generateUserAuthByPublicKeyRequestPacket(user, service);
sendPacket();
}
void SshSendFacility::sendUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
const QByteArray &service)
{
m_outgoingPacket.generateUserAuthByKeyboardInteractiveRequestPacket(user, service);
sendPacket();
}
void SshSendFacility::sendUserAuthInfoResponsePacket(const QStringList &responses)
{
m_outgoingPacket.generateUserAuthInfoResponsePacket(responses);
sendPacket();
}
void SshSendFacility::sendRequestFailurePacket()
{
m_outgoingPacket.generateRequestFailurePacket();
sendPacket();
}
void SshSendFacility::sendIgnorePacket()
{
m_outgoingPacket.generateIgnorePacket();
sendPacket();
}
void SshSendFacility::sendInvalidPacket()
{
m_outgoingPacket.generateInvalidMessagePacket();
sendPacket();
}
void SshSendFacility::sendSessionPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize)
{
m_outgoingPacket.generateSessionPacket(channelId, windowSize,
maxPacketSize);
sendPacket();
}
void SshSendFacility::sendDirectTcpIpPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize, const QByteArray &remoteHost, quint32 remotePort,
const QByteArray &localIpAddress, quint32 localPort)
{
m_outgoingPacket.generateDirectTcpIpPacket(channelId, windowSize, maxPacketSize, remoteHost,
remotePort, localIpAddress, localPort);
sendPacket();
}
void SshSendFacility::sendTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort)
{
m_outgoingPacket.generateTcpIpForwardPacket(bindAddress, bindPort);
sendPacket();
}
void SshSendFacility::sendCancelTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort)
{
m_outgoingPacket.generateCancelTcpIpForwardPacket(bindAddress, bindPort);
sendPacket();
}
void SshSendFacility::sendPtyRequestPacket(quint32 remoteChannel,
const SshPseudoTerminal &terminal)
{
m_outgoingPacket.generatePtyRequestPacket(remoteChannel, terminal);
sendPacket();
}
void SshSendFacility::sendEnvPacket(quint32 remoteChannel,
const QByteArray &var, const QByteArray &value)
{
m_outgoingPacket.generateEnvPacket(remoteChannel, var, value);
sendPacket();
}
void SshSendFacility::sendExecPacket(quint32 remoteChannel,
const QByteArray &command)
{
m_outgoingPacket.generateExecPacket(remoteChannel, command);
sendPacket();
}
void SshSendFacility::sendShellPacket(quint32 remoteChannel)
{
m_outgoingPacket.generateShellPacket(remoteChannel);
sendPacket();
}
void SshSendFacility::sendSftpPacket(quint32 remoteChannel)
{
m_outgoingPacket.generateSftpPacket(remoteChannel);
sendPacket();
}
void SshSendFacility::sendWindowAdjustPacket(quint32 remoteChannel,
quint32 bytesToAdd)
{
m_outgoingPacket.generateWindowAdjustPacket(remoteChannel, bytesToAdd);
sendPacket();
}
void SshSendFacility::sendChannelDataPacket(quint32 remoteChannel,
const QByteArray &data)
{
m_outgoingPacket.generateChannelDataPacket(remoteChannel, data);
sendPacket();
}
void SshSendFacility::sendChannelSignalPacket(quint32 remoteChannel,
const QByteArray &signalName)
{
m_outgoingPacket.generateChannelSignalPacket(remoteChannel, signalName);
sendPacket();
}
void SshSendFacility::sendChannelEofPacket(quint32 remoteChannel)
{
m_outgoingPacket.generateChannelEofPacket(remoteChannel);
sendPacket();
}
void SshSendFacility::sendChannelClosePacket(quint32 remoteChannel)
{
m_outgoingPacket.generateChannelClosePacket(remoteChannel);
sendPacket();
}
void SshSendFacility::sendChannelOpenConfirmationPacket(quint32 remoteChannel, quint32 localChannel,
quint32 localWindowSize, quint32 maxPacketSize)
{
m_outgoingPacket.generateChannelOpenConfirmationPacket(remoteChannel, localChannel,
localWindowSize, maxPacketSize);
sendPacket();
}
void SshSendFacility::sendChannelOpenFailurePacket(quint32 remoteChannel, quint32 reason,
const QByteArray &reasonString)
{
m_outgoingPacket.generateChannelOpenFailurePacket(remoteChannel, reason, reasonString);
sendPacket();
}
} // namespace Internal
} // namespace QSsh

View file

@ -0,0 +1,106 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "sshcryptofacility_p.h"
#include "sshoutgoingpacket_p.h"
#include <QStringList>
QT_BEGIN_NAMESPACE
class QTcpSocket;
QT_END_NAMESPACE
namespace QSsh {
class SshPseudoTerminal;
namespace Internal {
class SshKeyExchange;
class SshSendFacility
{
public:
SshSendFacility(QTcpSocket *socket);
void reset();
void recreateKeys(const SshKeyExchange &keyExchange);
void createAuthenticationKey(const QByteArray &privKeyFileContents);
QByteArray sendKeyExchangeInitPacket();
void sendKeyDhInitPacket(const Botan::BigInt &e);
void sendKeyEcdhInitPacket(const QByteArray &clientQ);
void sendNewKeysPacket();
void sendDisconnectPacket(SshErrorCode reason,
const QByteArray &reasonString);
void sendMsgUnimplementedPacket(quint32 serverSeqNr);
void sendUserAuthServiceRequestPacket();
void sendUserAuthByPasswordRequestPacket(const QByteArray &user,
const QByteArray &service, const QByteArray &pwd);
void sendUserAuthByPublicKeyRequestPacket(const QByteArray &user,
const QByteArray &service);
void sendUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
const QByteArray &service);
void sendUserAuthInfoResponsePacket(const QStringList &responses);
void sendRequestFailurePacket();
void sendIgnorePacket();
void sendInvalidPacket();
void sendSessionPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize);
void sendDirectTcpIpPacket(quint32 channelId, quint32 windowSize, quint32 maxPacketSize,
const QByteArray &remoteHost, quint32 remotePort, const QByteArray &localIpAddress,
quint32 localPort);
void sendTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort);
void sendCancelTcpIpForwardPacket(const QByteArray &bindAddress, quint32 bindPort);
void sendPtyRequestPacket(quint32 remoteChannel,
const SshPseudoTerminal &terminal);
void sendEnvPacket(quint32 remoteChannel, const QByteArray &var,
const QByteArray &value);
void sendExecPacket(quint32 remoteChannel, const QByteArray &command);
void sendShellPacket(quint32 remoteChannel);
void sendSftpPacket(quint32 remoteChannel);
void sendWindowAdjustPacket(quint32 remoteChannel, quint32 bytesToAdd);
void sendChannelDataPacket(quint32 remoteChannel, const QByteArray &data);
void sendChannelSignalPacket(quint32 remoteChannel,
const QByteArray &signalName);
void sendChannelEofPacket(quint32 remoteChannel);
void sendChannelClosePacket(quint32 remoteChannel);
void sendChannelOpenConfirmationPacket(quint32 remoteChannel, quint32 localChannel,
quint32 localWindowSize, quint32 maxPackeSize);
void sendChannelOpenFailurePacket(quint32 remoteChannel, quint32 reason,
const QByteArray &reasonString);
quint32 nextClientSeqNr() const { return m_clientSeqNr; }
private:
void sendPacket();
quint32 m_clientSeqNr;
SshEncryptionFacility m_encrypter;
QTcpSocket *m_socket;
SshOutgoingPacket m_outgoingPacket;
};
} // namespace Internal
} // namespace QSsh

View file

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

View file

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

View file

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

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more